How Tool Permissions Work in Goose: Auto, Approve, Chat, and SmartApprove Modes
Goose determines whether to execute tool calls based on the active operating mode, user-defined permission levels in permission.yaml, and read-only annotations or LLM-based safety detection, with Auto allowing everything, Chat blocking all tools, and SmartApprove using a layered decision hierarchy.
In the block/goose open-source agent framework, tool permissions are governed by a configurable permission inspector that balances automation with security. The system supports four distinct execution modes—Chat, Auto, Approve, and SmartApprove—each implementing different trust boundaries for tool invocation. Understanding how these modes interact with user-defined policies and tool metadata is essential for deploying Goose safely in production environments.
The Four Goose Operating Modes
Goose's behavior is controlled by the GooseMode enum defined in crates/goose/src/config/goose_mode.rs. Each variant represents a distinct security posture for tool execution.
Chat Mode
When configured to GooseMode::Chat, Goose operates as a conversational agent without tool privileges. This mode blocks all tool calls entirely, ensuring that the agent cannot read files, execute commands, or modify system state. It is the most restrictive configuration and serves as a safe default for pure question-answering scenarios.
Auto Mode (Unconditional Approval)
In GooseMode::Auto, defined in goose_mode.rs as "Automatically approve tool calls", the permission inspector immediately returns InspectionAction::Allow for every tool request without evaluation. According to the implementation in crates/goose/src/permission/permission_inspector.rs (lines 39-42), this mode bypasses all permission checks, annotations, and LLM detection, generating the reason "Auto mode – all tools approved". No prompts are presented to the user regardless of the tool's destructive potential.
Approve Mode (User Permissions Only)
GooseMode::Approve enforces strict user control by consulting only the permission.yaml configuration. When the inspector encounters this mode (lines 42-77 in permission_inspector.rs), it checks permission_manager.get_user_permission(tool_name) for each request. If the user has defined AlwaysAllow, the tool executes; NeverAllow results in immediate denial; and AskBefore triggers an interactive approval prompt. Notably, this mode ignores read-only annotations and LLM-based safety detection.
SmartApprove Mode (Layered Safety)
GooseMode::SmartApprove, described as "Ask only for sensitive tool calls", implements a sophisticated five-layer decision hierarchy in the PermissionInspector::inspect method. This mode combines explicit user policies with automated safety analysis to minimize interruptions while preventing destructive operations.
The SmartApprove Decision Hierarchy
When running in SmartApprove mode, Goose evaluates tool requests through a prioritized cascade defined in crates/goose/src/permission/permission_inspector.rs:
-
User-Defined Permissions – The system first consults
permission_manager.get_user_permission(tool_name)frompermission.yaml. AnAlwaysAllowentry permits immediate execution,NeverAllowblocks the call, andAskBeforeforces a prompt. -
Read-Only Annotations – If no user policy exists, the inspector checks
self.is_readonly_annotated_tool(tool_name)or cached SmartApprove entries (permission_manager.get_smart_approve_permission(tool_name) == Some(AlwaysAllow)). Tools explicitly marked as read-only in their metadata bypass approval requirements. -
Extension Management Protection – The hard-coded
MANAGE_EXTENSIONS_TOOL_NAME_COMPLETEtool always triggersRequireApprovalwith a custom message, preventing accidental agent self-modification. -
LLM Read-Only Detection – For unknown tools without cached entries, Goose defers to an LLM-based detector implemented in
crates/goose/src/permission/permission_judge.rs. Thedetect_read_only_toolsfunction analyzes the tool's description and parameters to determine if it mutates state. -
Default Denial – If no previous layer provides a definitive answer, the system defaults to
RequireApproval, prompting the user before execution.
The LLM detector's decisions are cached in permission.yaml under the smart_approve key, ensuring that subsequent invocations of the same tool type do not incur repeated inference costs.
Configuring Tool Permissions
Switching Modes via CLI
Control the active permission model using the Goose configuration interface:
# Enable full automation (all tools execute without prompts)
goose config set-mode auto
# Enable smart filtering (safe tools auto-approve, dangerous tools ask)
goose config set-mode smart-approve
# Require explicit approval for every tool (except user-defined exceptions)
goose config set-mode approve
# Disable all tool access (conversation only)
goose config set-mode chat
Editing permission.yaml
User and cached permissions persist in $CONFIG_DIR/permission.yaml (typically ~/.config/goose/permission.yaml). The PermissionManager loads this file on startup (see PermissionManager::new in crates/goose/src/config/permission.rs):
user:
always_allow:
- shell
- read_file
ask_before:
- google_search
- fetch_url
never_allow:
- system_shutdown
- rm_rf_root
smart_approve:
always_allow:
- list_directory
- git_status
ask_before:
- git_commit
- write_file
Entries under user apply to Approve and SmartApprove modes, while smart_approve stores cached LLM decisions used only in SmartApprove.
Annotating Tools with Read-Only Hints
Tool developers can signal safety to the permission inspector by adding metadata when defining tools in Rust. In crates/goose/src/permission/permission_inspector.rs, the apply_tool_annotations method checks for these hints:
use goose::models::tool::Tool;
use mcp_core::tool::Annotations;
let tool = Tool {
name: "read_file".into(),
// ... other fields ...
annotations: Some(Annotations {
read_only_hint: Some(true), // Declares this tool never modifies state
..Default::default()
}),
};
When read_only_hint is true, SmartApprove mode automatically allows the tool without LLM consultation or user prompts.
Programmatic Inspection
The decision matrix is validated by unit tests in crates/goose/src/permission/permission_inspector.rs (lines 70-80), demonstrating the exact behavior of each mode combination:
#[test_case(GooseMode::Auto, false, None, InspectionAction::Allow; "auto_allows")]
#[test_case(GooseMode::SmartApprove, true, None, InspectionAction::Allow; "smart_approve_annotation_allows")]
#[test_case(GooseMode::SmartApprove, false, Some(PermissionLevel::AlwaysAllow), InspectionAction::Allow; "smart_approve_cached_allow")]
#[test_case(GooseMode::Approve, false, None, InspectionAction::RequireApproval(None); "approve_requires_approval")]
fn test_inspect_action(mode: GooseMode, is_readonly: bool, cached: Option<PermissionLevel>, expected: InspectionAction) {
// Test implementation verifies the decision logic
}
Summary
- Chat mode blocks all tool execution, providing a safe conversational-only environment.
- Auto mode unconditionally allows every tool call via
InspectionAction::Allowwithout consulting permissions or annotations. - Approve mode relies solely on user-defined permissions in
permission.yaml, ignoring automated safety detection. - SmartApprove mode implements a five-layer hierarchy: user permissions override read-only annotations, which override LLM detection, with a final default to requiring approval.
- The
PermissionInspector::inspectmethod incrates/goose/src/permission/permission_inspector.rsorchestrates these decisions, whilePermissionManagerincrates/goose/src/config/permission.rshandles persistence.
Frequently Asked Questions
How does SmartApprove decide if a new tool is safe?
SmartApprove first checks if you have explicitly configured the tool in permission.yaml under user permissions. If not, it looks for a read_only_hint: true annotation in the tool definition. If neither exists, it sends the tool description to an LLM detector (permission_judge.rs) to analyze whether the tool modifies state. The LLM's verdict is then cached in permission.yaml under smart_approve to avoid repeated inference.
What is the difference between Approve and SmartApprove modes?
Approve mode only respects explicit user permissions defined in permission.yaml and treats all unknown tools as requiring approval. SmartApprove adds automated safety analysis: it checks read-only annotations and uses an LLM to detect read-only tools, allowing safe tools to run automatically while still prompting for potentially dangerous operations. Approve mode never uses LLM detection.
Where are permission settings stored?
User-defined and cached permissions are stored in the permission.yaml file located in your system's configuration directory ($CONFIG_DIR/goose/permission.yaml, typically ~/.config/goose/permission.yaml on Linux/macOS). The PermissionManager struct in crates/goose/src/config/permission.rs handles reading and writing this file.
Can I override SmartApprove automatic decisions?
Yes. User permissions in the user section of permission.yaml always take precedence over SmartApprove's automated logic. If you set a tool to always_allow or never_allow under the user key, Goose will follow that directive regardless of read-only annotations or LLM analysis. Additionally, you can pre-seed the smart_approve section to cache specific permission levels before first use.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →