# How Tool Permissions Work in Goose: Auto, Approve, Chat, and SmartApprove Modes

> Understand Goose tool permissions. Explore Auto, Approve, Chat, and SmartApprove modes to control tool execution based on YAML settings and safety detection.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: deep-dive
- Published: 2026-04-05

---

**Goose determines whether to execute tool calls based on the active operating mode, user-defined permission levels in [`permission.yaml`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/permission.yaml) configuration. When the inspector encounters this mode (lines 42-77 in [`permission_inspector.rs`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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:

```bash

# 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`](https://github.com/block/goose/blob/main/crates/goose/src/config/permission.rs)):

```yaml
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`](https://github.com/block/goose/blob/main/crates/goose/src/permission/permission_inspector.rs), the `apply_tool_annotations` method checks for these hints:

```rust
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`](https://github.com/block/goose/blob/main/crates/goose/src/permission/permission_inspector.rs) (lines 70-80), demonstrating the exact behavior of each mode combination:

```rust
#[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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/crates/goose/src/permission/permission_inspector.rs) orchestrates these decisions, while `PermissionManager` in [`crates/goose/src/config/permission.rs`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/permission_judge.rs)) to analyze whether the tool modifies state. The LLM's verdict is then cached in [`permission.yaml`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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.