Docs / Autopilot / Modes and permissions

Modes and permissions

How the plan, edit and auto modes decide which tool calls run without asking, and how to set permission rules for headless runs.

Modes are off by default

Plan, edit and auto modes are behind a setting that is off by default. With modes off, every turn runs in auto: tool calls run without per-call prompts, and the mode controls (Shift+Tab, /mode, the mode badge) are hidden.

Turn modes on:

/settings modes on

This starts you in edit. Turn them off again with /settings modes off. The setting is stored as modesEnabled in the config file.

The three modes

Mode What runs without asking Use it for
plan Read-only tools only (read, glob, grep, web search, read-only GitHub, browser fetch). Writes, edits, mutating shell commands, MCP tools and LSP renames are blocked. Research and planning before you change anything
edit Read-only tools. Each mutating call asks for approval. Day-to-day work where you review each change
auto Everything. Trusted tasks

Switch mode

Action Effect
Shift+Tab Cycle edit, plan, auto
/mode edit, /mode plan, /mode auto Switch directly
/plan, /edit, /auto Short forms

Plan mode and shell commands

In plan mode, the bash tool only runs commands it can classify as pure reads. Plain pipes and && are allowed. Redirection, ;, ||, a lone &, command substitution, variable expansion and backslash escapes are refused. Each command must be on an allowlist (for example ls, cat, grep, rg, wc, diff and jq) and must not use an option that runs a program or writes a file. The allowlist is in src/mode.ts.

Protected reads and local network access

In plan and edit:

  • Reading a credentials file asks first. This covers ~/.ssh, ~/.aws, Autopilot's own config with your provider keys, .env and /proc/*/environ. A prompt injection then cannot read a key and send it out with web_fetch.
  • web_fetch and browser_fetch ask before reaching a local or private network address: localhost, 127.0.0.1, 10.x, 192.168.x, and cloud metadata at 169.254.169.254, including names and redirects that resolve there. Approval is per origin.

The permission prompt

When a call needs approval, a prompt appears in the UI:

  • Navigate with ↑/↓ or j/k, or choose directly with Alt+1, Alt+2, Alt+3.
  • Denying a call opens inline feedback, so you can tell the agent what to do instead.
  • When the agent launches several subagents in one step, one prompt covers the whole batch.

Loop guardrails

If every tool call in a turn is blocked, the agent is given a recovery instruction. If that happens again, the turn ends with a tool-free summary instead of retrying. This stops a turn from spending tokens on calls that will never run.

Permission rules for headless runs

In print mode (-p) there is no one to answer a prompt. A call that would ask is denied, unless one of these decides it first:

  1. --dangerously-allow-all approves every call.
  2. A rule in the permissions config key.
  3. A PermissionRequest hook.

Rules are keyed by tool name. Each maps a pattern to allow, deny or ask. Patterns match the target path for file tools, and the command string for bash.

{
  "permissions": {
    "bash": { "npm test": "allow", "**": "ask" },
    "write": { "**": "deny" },
    "edit": { "~/trusted/**": "allow", "**": "ask" }
  }
}
  • The longest (most specific) matching pattern wins.
  • With no matching rule, the result is ask, which is denied in print mode.
  • A pattern matches a target that starts with it, or that matches it as a glob.
  • For bash, an allow prefix must end at a word boundary (npm test allows npm test -- --watch, not npm testify), and the rest of the command may contain no shell control syntax such as ;, &, |, `, $( or >. A pattern made only of wildcards (**) still allows every command.
  • deny and ask rules also match each command inside a compound command, so cd x && rm -rf / matches a rule for rm -rf.

The evaluator is src/permissions-evaluator.ts.

Pushing to the default branch

By default the bash tool blocks git push to the repository's default branch and directs the model to open a pull request instead. Set "allowDirectPush": true to allow it. preferPullRequests (default true) adds the same preference to the system prompt.

In the SDK

createAgentSession takes a permissionHandler that decides each request, and session.setMode("plan" | "edit" | "auto") changes the mode. A single prompt can override it with prompt(text, { mode }). See SDK and RPC.