Skip to content
Agentic Control Plane

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; for the ranked what-should-I-use list, the 2026 options guide.

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 — 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 fragileBash(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 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.
  • 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 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 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.