Skip to content

Permissions

Permissions are how you keep Anode useful without giving it the keys to everything. Every tool call is checked before it runs.

Read operations run automatically. File mutations require confirmation unless policy or unrestricted approval allows them. Headless execution defaults to ask; the TUI starts in auto, which auto-allows non-destructive shell commands but still prompts for writes and destructive-looking commands.

You control behavior with three layers:

  • approval mode;
  • JSON permission rules;
  • named presets.

Out of the box, Anode asks before doing anything destructive:

  • Auto-allowed - reading files, searching code, browsing the repo.
  • Requires confirmation - creating files, editing files, and shell commands that are not allowed by the active approval mode or policy.

You see a confirmation prompt with the tool name and arguments. Press y to approve or n to reject.

Each built-in tool has an assigned permission level.

LevelDescriptionExample Tools
auto_readAlways allowed. Read-only operations.read, multi_read, finder, glob, grep, chart, todo_read
external_readExternal service or delegated read-only investigation. Allowed by default.web_search, oracle, code_review
state_writeLocal agent state mutations. Allowed by default.todo_write, task_list, send_message
confirm_executeRequires confirmation. Shell or executable execution.bash, most toolbox tools
confirm_writeRequires confirmation. File mutations.edit_file, create_file, apply_patch
delegatedDelegated to subagent or skill.task, skill, handoff
interactiveRequires user interaction.ask_user
unavailableTool is disabled.-

Control how Anode handles confirmation prompts.

ModeBehavior
askAsk for confirmation on confirm_execute and confirm_write tools. This is the headless default.
autoAuto-allow non-destructive shell commands. Shell commands that look destructive still ask. File writes still ask unless a permission rule explicitly allows them.
unrestrictedNever ask for confirmation. Everything runs immediately. yolo is an alias for unrestricted.

Switch from the CLI:

anode -x --approval auto "run tests and summarize failures"
anode execute "refactor the auth module" --approval unrestricted

Switch in the TUI:

/approval

Cycles through ask -> auto -> unrestricted.

Write fine-grained rules to allow, reject, or gate specific tool calls. Rules are JSON arrays of objects.

Add rules from the CLI:

anode permissions add allow read
anode permissions add reject bash --match 'command=rm -rf *' --message "No destructive rm"
anode permissions add ask edit_file --match 'path=*.lock' --workspace
anode permissions add delegate bash --to reviewer --context child_run

permissions add prepends the new rule so it takes priority. By default it updates the user config at ~/.config/anode/config.json without round-tripping provider credentials discovered from environment variables. Use --workspace to write .agents/permissions.json.

{
"tool": "bash",
"matches": {
"command": "rm -rf *"
},
"action": "reject",
"context": "main_run",
"message": "Destructive rm is not allowed"
}
FieldTypeDescription
toolstringGlob pattern matching the tool name. Default: "*".
matchesobjectKey-value pairs matched against call arguments. Supports string globs and array alternatives.
actionstringallow, reject, ask, or delegate.
contextstringmain_run or child_run. Omit to match both.
messagestringMessage shown when a reject, ask, or delegate rule fires.
tostringDelegate target label for delegate rules.

In normal engine-backed runs, delegate routes the matching call through a child run using the profile named by to. Direct tool runners that do not wire a delegation handler fail clearly instead of running the original call.

The tool field and values in matches support glob patterns.

[
{
"tool": "bash",
"matches": { "command": "git *" },
"action": "allow"
},
{
"tool": "mcp__*",
"action": "ask"
},
{
"tool": "edit_file",
"matches": { "path": "*.lock" },
"action": "reject",
"message": "Do not modify lockfiles"
}
]

Use an array to match any of several values:

{
"tool": "bash",
"matches": {
"command": ["npm test", "npm run lint", "npm run build"]
},
"action": "allow"
}

Rules are evaluated in priority order. The first matching rule wins.

PrioritySourcePath
1 (highest)User config~/.config/anode/config.json -> permissions array
2Project config.agents/permissions.json
3Project config fallback.anode/permissions.json, used only when .agents/permissions.json is absent or unreadable
4 (lowest)Preset rulesFrom the active permissionPreset

Place your most specific rules in user config. Place shared team rules in .agents/permissions.json and commit them to the repo.

Set a preset in ~/.config/anode/config.json:

{
"permissionPreset": "careful"
}

Allow everything. No confirmation prompts.

[
{ "tool": "*", "action": "allow" }
]

Reject destructive commands. Ask before compound, redirected, backgrounded, or command-substituted shell commands. Allow read-only git commands, common read-only shell inspection commands, and read/search helper tools.

[
{ "tool": "bash", "matches": { "command": ["rm -rf*", "rm -fr*", "rm -Rf*", "rm -r -f*", "rm -f -r*", "rm --recursive --force*", "rm --force --recursive*"] }, "action": "reject" },
{ "tool": "bash", "matches": { "command": ["git reset --hard*", "git reset * --hard*"] }, "action": "reject" },
{ "tool": "bash", "matches": { "command": ["git push --force*", "git push * --force*", "git push -f*", "git push * -f*"] }, "action": "reject" },
{ "tool": "bash", "matches": { "command": ["git clean -*f*", "git clean * -*f*"] }, "action": "reject" },
{ "tool": "bash", "matches": { "command": "drop table*" }, "action": "reject" },
{ "tool": "bash", "matches": { "command": "drop database*" }, "action": "reject" },
{ "tool": "bash", "matches": { "command": ["*;*", "*&*", "*||*", "*|*", "*>*", "*<*", "*$(*", "*`*", "*\n*"] }, "action": "ask" },
{ "tool": "bash", "matches": { "command": "git diff*" }, "action": "allow" },
{ "tool": "bash", "matches": { "command": "git log*" }, "action": "allow" },
{ "tool": "bash", "matches": { "command": "git status*" }, "action": "allow" },
{ "tool": "bash", "matches": { "command": "git show*" }, "action": "allow" },
{ "tool": "bash", "matches": { "command": ["ls", "ls *", "pwd", "pwd *", "cat", "cat *", "head *", "tail *", "grep *", "rg *", "wc *"] }, "action": "allow" },
{ "tool": "read", "action": "allow" },
{ "tool": "finder", "action": "allow" },
{ "tool": "glob", "action": "allow" },
{ "tool": "grep", "action": "allow" }
]

Ask for confirmation on every tool call.

[
{ "tool": "*", "action": "ask" }
]

Verify how your policy handles a specific tool call before running it for real.

# Test a bash command
anode permissions test bash --command "rm -rf /tmp/build"
# Test a file edit
anode permissions test edit_file --path "src/main.go"
# Test in child_run context
anode permissions test bash --command "npm test" --context child_run
# Test a rejected preset without editing config
anode permissions test bash --preset careful --command "rm -rf tmp"
# Test an MCP tool
anode permissions test mcp__github__create_issue
# Test with arbitrary arguments
anode permissions test bash --arg timeout_ms=5000 --command "make build"

The shorthand form anode permissions <tool> accepts the same test flags. Without a tool name, anode permissions lists rules and rejects test-only flags instead of ignoring them.

The output shows which rule matched, the action taken, and the message (if any).

MCP tools (mcp__<server>__<tool>) and toolbox tools (tb__<name>) pass through the same permission policy as built-in tools. Write rules targeting their full names:

[
{
"tool": "mcp__github__*",
"action": "allow"
},
{
"tool": "tb__deploy",
"action": "ask",
"message": "Confirm before deploying"
},
{
"tool": "mcp__*",
"action": "reject",
"context": "child_run",
"message": "MCP tools blocked in subagents"
}
]

Subagents (spawned via the task tool) run in child_run context. Use the context field to scope rules to subagents only.

[
{
"tool": "bash",
"action": "reject",
"context": "child_run",
"message": "Subagents cannot run shell commands"
},
{
"tool": "edit_file",
"matches": { "path": "migrations/*" },
"action": "reject",
"context": "child_run",
"message": "Subagents cannot modify migrations"
}
]

Rules without a context field match both main_run and child_run.

  • Configuration - config file locations and the full settings reference.
  • Providers - set up the LLM providers that power your agent.
  • Tools - the built-in tools that permissions govern.
  • Profiles - bind tool allowlists and read-only posture to agent profiles.