# Claude Code Permissions & Control Model, Explained

The six permission modes including the new auto mode, how allow/deny rules actually match, what the sandbox bounds, which hooks fire when, what bypassPermissions still enforces, and where the native model ends. Current to v2.1.233.

# The Claude Code control model, explained

Claude Code has the most mature permission system of any coding agent — six modes, four settings scopes, pattern rules, an OS sandbox, the ecosystem's reference hook API, and (since July 2026) a classifier that screens actions instead of prompting you. It's also the control surface changing fastest: the default mode for paid personal accounts changed *this week*. This page is the current reference — what each mechanism actually does, the precedence between them, and the documented limits. Current to v2.1.233.

*This page covers Claude Code's own controls. For wiring ACP into Claude Code, see the [install guide](/integrations/claude-code); for the ranked what-should-I-use list, [the 2026 options guide](/blog/best-ways-to-control-claude-code-2026).*

## The six modes

| Mode | What runs without asking |
|---|---|
| `default` (UI: **Manual**) | Reads. Everything else prompts. |
| `acceptEdits` | Reads, file edits, and common filesystem commands (`mkdir`, `mv`, `cp`, `rm`, `sed`…) inside the workspace; protected paths still prompt. |
| `plan` | Nothing side-effectful — read-only planning (classifier-approved commands too, unless you disable `useAutoModeDuringPlan`). |
| `auto` | Everything the **classifier** approves. GA July 10, 2026; **default for Pro/Max/Team since August 14, 2026** (v2.1.228). Enterprise, API-key, and headless sessions still default to Manual. |
| `dontAsk` | Only pre-approved tools; everything else auto-denied, never prompts. The CI mode. |
| `bypassPermissions` | Everything — with carve-outs that still hold (below). |

Switch with `--permission-mode` or Shift+Tab (auto and bypass only appear in the cycle when enabled; `dontAsk` never does). Two org-level kill switches exist: `permissions.disableBypassPermissionsMode` and `permissions.disableAutoMode`.

**Auto mode** is the headline change of 2026: a second model (Sonnet 5 by default) reviews each action against a prose trust profile instead of prompting you. Anthropic's stated rationale is blunt — in their data, humans caught 13.6% of dangerous commands at the prompt; the classifier caught 89%. Approval fatigue isn't a UX complaint, it's a measured security failure, and auto mode is the response. It runs as a *second* gate (hooks → permission rules → classifier), reads its config only from user and managed scope (a repo can't grant itself trust), and falls back to prompting after 3 consecutive or 20 total blocks. We've written a [full teardown of what it does and doesn't cover](/blog/claude-code-auto-mode-wrote-a-policy) — including the setup flow that mines your transcripts to *write* the trust profile.

## The rules

Rules live in `permissions.allow` / `ask` / `deny` arrays across four scopes — user (`~/.claude/settings.json`), project (`.claude/settings.json`), local (`.claude/settings.local.json`), and managed (org-deployed, including MDM and server-managed variants). Precedence is the part worth memorizing:

- Evaluation order is **deny → ask → allow**, first match wins.
- **A deny at any scope beats an allow at any scope.** Managed denies can't be overridden, even by CLI flags.
- Deny rules survive `bypassPermissions`. If a rule must hold, make it a deny.

Syntax spans tools and arguments: `Bash(npm run test *)`, `Read(~/.zshrc)`, `WebFetch(domain:example.com)`, `mcp__github__get_*`. Sharp edges the docs themselves flag: shell operators split compound commands and each piece must match independently, known-safe wrappers are stripped, but **argument-constrained Bash patterns are officially called fragile** — `Bash(curl http://github.com/ *)` misses the `-X GET` spelling, the `https://` spelling, and the env-var indirection. The documented advice is ours too: deny the whole command family, and put anything that must hold in a hook or the sandbox, not a string pattern. (Also: `Edit()` rules govern Write and NotebookEdit; `Write()` rules are silently never consulted — a warning was added in v2.1.210 because enough people had unenforced rules.)

Where do "don't ask again" clicks go? **Bash approvals persist permanently, per repo**, into `.claude/settings.local.json` at the repo root. File-edit approvals are session-only. Your local settings file is an accumulating policy written one approval at a time — worth reading occasionally, because you authored it without noticing.

## The sandbox

OS-level (Seatbelt on macOS, bubblewrap on Linux), **off by default** — enable with `/sandbox`. Defaults once on: writes confined to the workspace and `$TMPDIR`, **reads unconfined** (the docs explicitly warn `~/.ssh` and `~/.aws/credentials` are readable unless you add deny-read rules), network through a proxy with no domains pre-allowed. A hardening detail we like: a set of paths is write-protected with no exemption possible — settings files, hook and skill directories, `.mcp.json`, credentials, shell rc files, `.git/hooks` — the harness protecting its own control surface from the agent it runs. New in 2026: credential masking (secrets replaced by sentinels inside the sandbox, re-injected at the proxy) and a `strictAllowlist` network mode. Documented limits: the default proxy trusts the client-supplied hostname (domain fronting is possible), the model can request `dangerouslyDisableSandbox` through the normal permission flow, and `excludedCommands` has no managed lockdown.

## The hooks

The hook API is the ecosystem's reference — Codex ships a deliberately compatible schema, Cursor honors exit-2 blocking, and Hermes accepts the JSON shapes. The 2026 event list is broad (PreToolUse, PostToolUse, the new PermissionRequest and PermissionDenied, subagent and session lifecycle, config-change events), with five handler types including HTTP and MCP-tool handlers.

Control facts that matter:

- **PreToolUse fires on every tool call.** There is no allow path that skips hooks — an auto-mode approval, an allow rule, and bypass mode all still traverse them. Exit code 2 or `permissionDecision: "deny"` blocks unconditionally.
- **A hook allow cannot override deny/ask rules** — hooks can only tighten, not loosen. Right default.
- **A timed-out hook does not block.** The docs say it plainly: don't count on a stalled hook as a gate. Hooks are fail-open on timeout — if your enforcement lives in a hook, its availability is part of your security posture.

## The record

Per-session JSONL transcripts (default 30-day retention), and — the underrated feature — opt-in OpenTelemetry with a **`tool_decision`** event recording each decision and its *source*: config rule, hook, or user choice. That's a real decision record, the strongest native one among the big three. Its limits are structural: opt-in, per-machine, and (transcripts especially) authored by the process it describes. Auto mode blocking writes to its own transcript files (v2.1.205) is a nice touch that acknowledges the problem.

## The escape hatch, precisely

`--dangerously-skip-permissions` (≡ `bypassPermissions`) removes every prompt. Still enforced: deny rules at all scopes, explicit ask rules, PreToolUse hook blocks, MCP tools flagged as requiring interaction, and a circuit breaker that catches `rm -rf /` and `rm -rf ~` even inside `$(…)`. It refuses to run as root. So bypass mode isn't "no controls" — it's "exactly your denies and your hooks, and nothing else." The December home-directory wipe ran under this flag; the circuit breaker that would catch it today was widened afterward. Write the denies before you need them.

## The empty chair

Claude Code defines unattended behavior better than most: `dontAsk` mode auto-denies everything not pre-approved, headless `-p` sessions start in Manual (excluded from the auto-mode default), and an approval nobody can answer resolves to a deny rather than a hang. The SDK adds `--permission-prompt-tool` to route asks to your own approver. One sharp edge the docs flag: `-p` counts as having accepted workspace trust — a scripted invocation in a hostile repo inherits that repo's project settings unless you strip them (`--setting-sources user`). [The empty-chair test](/blog/interactive-vs-autonomous-the-empty-chair-test) walks the general version of this reasoning.

## Where the native model ends

Claude Code's native model is the most complete in the ecosystem, and 2026 made it substantially stronger. What's structurally out of scope:

- **Policy is layered files on one machine** (managed settings excepted, for orgs that deploy them). The local settings file accumulates approvals invisibly; two machines drift.
- **The classifier explains nothing.** Auto mode's denials read `Blocked by classifier` — no rule, no reason, no queryable trail of what it allowed. [Full analysis here](/blog/claude-code-auto-mode-wrote-a-policy).
- **The record is opt-in and local.** Transcripts age out in 30 days by default; the OTel decision stream is off until someone turns it on, and nothing leaves the machine otherwise.
- **Rules don't travel.** Your carefully-written denies exist for Claude Code only — not for the [other harnesses](/controls) your team also runs.

The composition we run ourselves: Claude Code's denies and sandbox as the local floor, auto mode for prompt fatigue, and [a hook on the call path](/integrations/claude-code) carrying workspace policy — same rules on every machine and every harness, every decision in a ledger the agent didn't author, asks that reach a human wherever they are. The hook seam is native, fires on every call, and deny-wins is guaranteed by the harness itself.

## Frequently asked questions


