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:

  1. User-Defined Permissions – The system first consults permission_manager.get_user_permission(tool_name) from permission.yaml. An AlwaysAllow entry permits immediate execution, NeverAllow blocks the call, and AskBefore forces a prompt.

  2. 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.

  3. Extension Management Protection – The hard-coded MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE tool always triggers RequireApproval with a custom message, preventing accidental agent self-modification.

  4. 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. The detect_read_only_tools function analyzes the tool's description and parameters to determine if it mutates state.

  5. 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::Allow without 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::inspect method in crates/goose/src/permission/permission_inspector.rs orchestrates these decisions, while PermissionManager in crates/goose/src/config/permission.rs handles 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →