Skip to content

Permissions, sandboxing & approval modes

You’re two settings into the agent’s lifecycle and you’ve already hit the wall. Either it pops up a confirmation dialog on every single command - and you spend the session pressing y, y, y until you stop reading what you’re approving - or you’ve turned approvals off and now there’s nothing between the agent and rm -rf node_modules in the wrong directory.

Both postures give a single answer to a set of actions that don’t share one:

The agent wants to… Your honest answer
Read a file, grep the repo Fine, every time
Run the test suite Fine, every time
Edit your code Usually fine
Run a one-off shell command Depends entirely on the command
Push to a remote Tell me first
chmod a system file No

Collapsing that column into a single “do you approve?” prompt is what produces both the fatigue and the accidents. Permissions let you answer it once, declaratively: which tools are always fine, which always need you in the loop, which are flatly forbidden. The agent then runs unattended through the boring 80% and only interrupts you for the genuinely risky 20%, and the things you flatly forbid are refused rather than offered to you as a prompt you might click through at 6pm.

Worth setting expectations before the detail: in most of these tools a deny rule is a pattern match at the tool-dispatch layer, not an OS-level wall. Commands can be chained, aliased, or wrapped in a script, and a rule written against one spelling won’t catch another. That’s why the sandbox row in the comparison below matters independently of the rules row - the rules encode intent, the sandbox is what holds when the intent is evaded. Treat permissions as the first layer of defence, not the only one.

Many tools in scope expose some form of allow, ask, and deny decision, but they place the boundary differently. Pi, for example, has no native per-tool approval prompt, and Copilot’s controls vary by product surface. Use the comparison and tool tabs as the source of truth rather than assuming one universal policy model.

What they don’t share is the shape. This is the most divergent area in the section - different mental models, different vocabulary, different layers, and no two tools put the gate in the same place. The Comparison table below is the map between them.

Real-world examples of permission rules people actually set:

  • Always allow - reads (Read, Grep, Glob), test runs (pnpm test, pytest), formatters (prettier, ruff), git status/log/diff.
  • Ask first - any git push, any git rebase, deletions, anything touching node_modules or package-lock.json, npm install/pip install.
  • Always deny - rm -rf, edits to .env or secrets, writes outside the project root, sudo, chmod, pushing to main directly.
  • Scoped allow patterns - Bash(npm run *) allowed, Bash(npm install *) asked. Same tool, different command shapes.
  • Per-agent profiles (OpenCode) - your plan agent is read-only no matter what; your build agent has full edit access; a custom security-review agent has read + a narrow shell allowlist.
  • Org-wide policy - managed config forbids any tool that can touch prod for everyone in the team, regardless of what they set locally.

The first three tiers, as they’d sit in a config:

allow: Read Grep Glob Bash(pnpm test) Bash(git status) Bash(prettier *)
ask: Bash(npm install *) Bash(git push *) Edit(package-lock.json)
deny: Bash(rm -rf *) Bash(sudo *) Edit(.env) Write(outside the project root)

The test: if you’ve ever caught yourself approving a command without actually reading it, your permission rules are too tight. If you’ve ever discovered the agent did something you wouldn’t have approved, they’re too loose.

Watch the 2-minute version

Permissions, sandboxing & approval modes. Watch on YouTube

Those mental models, side by side: Claude Code walks allow/ask/deny rule lists, Codex passes two gates in sequence (sandbox, then approval policy), and OpenCode looks up a per-agent permission map. Pick a tool, set the policy, and trace a command through it.

Rules live in settings.json as allow / ask / deny lists. Evaluation order: deny beats ask beats allow; anything unmatched falls through to the default (ask). Example policy:

{
  "permissions": {
    "deny":  ["Bash(rm -rf:*)", "Edit(.env)", "Read(.env)"],
    "ask":   ["Bash(git push:*)", "Bash(npm install:*)"],
    "allow": ["Bash(npm run test:*)", "Read(src/**)", "Bash(git status)"]
  }
}
The agent tries:
  1. deny list No deny rule matches.
  2. ask list No ask rule matches.
  3. allow listBash(npm run test:*) Allow rule matches - the command runs without a prompt.
Result: runs without asking
You want to… Reach for Not
Decide broadly what the agent can do unattended Permissions Hooks
Block one specific command pattern with logic (regex against args, conditional logic) Hook on PreToolUse Permissions alone
Take the agent fully read-only for one session Plan mode Toggling every permission
Restrict a worker subagent’s tools Subagent tools: field Session-wide permissions
Enforce a rule across the whole org Managed/policy config Per-user settings

Permissions are coarse and declarative - “Bash is ask, Read is allow.” Hooks are fine and procedural - “if the bash command matches git push.*main, block it and log it.” Use permissions for the policy; reach for hooks when the policy needs logic.

The simulator models Claude Code, Codex, and OpenCode. Cursor, Copilot, and Pi are shown as explicit coverage boundaries; use their tabs and the comparison table for their current documented behavior rather than reading an unmodeled state as “no permissions.”

Showing: Claude Code pick a tab to change

Permission rules live in settings.json (project, user, or managed). Each rule targets a tool with allow / ask / deny:

{
"permissions": {
"allow": ["Bash(npm run test:*)", "Read(src/**)"],
"deny": ["Bash(rm:*)", "Edit(.env)"],
"ask": ["Bash(*)"]
}
}

Layering: managed policy sits above everything, then CLI flags, then project-local, then project-shared, with your user settings last. An org admin can deny tool patterns globally; project settings can broaden within those limits. Permission rules are also a special case of the general order - they merge across levels rather than one file wholesale replacing another, and a deny stays denied whichever level set it.

Default mode: from v2.1.283 a plain claude in the terminal or VS Code starts in auto mode (a classifier reviews actions) on every plan and provider. claude -p and Agent SDK runs start in manual (default) mode from v2.1.302, where a call that needs approval is denied unless a rule, hook or callback allows it. Set permissions.defaultMode or --permission-mode to change it; auto and bypassPermissions are ignored in project settings files.

Plan mode (Shift-Tab) normally prevents source mutations as a posture; bypassPermissions can override that protection - see Plan mode.

Claude Code’s permission rules are enforced at the tool-dispatch layer. The optional sandbox adds OS-level Bash isolation; use both when the filesystem or network must be contained.

Aspect Claude Code Codex OpenCode Cursor Copilot Pi
Primary axis Permission mode + allow/ask/deny rules per tool Sandbox tier × approval policy Per-tool per-agent IDE run mode plus CLI project permissions Per-call approval + auto-approve knobs Trust gate on project resources; no per-tool permission popups
OS-level sandbox Yes, for shell commands only, and off by default (sandbox.enabled or /sandbox; Seatbelt on macOS, bubblewrap on Linux/WSL2). Separate from the allow/ask/deny rules Yes (read-only / workspace-write / danger-full-access) No (wrap in a container if you need it) Yes (Seatbelt on macOS, Landlock and seccomp on Linux; Cursor v2.0+) - a layer on top of the run modes, not a mode of its own Optional (chat.agent.sandbox.enabled in VS Code; /sandbox enable in the CLI) Via container/extension, not built-in
Approval modes Named modes over allow/ask/deny rules: default (shown as Manual) / acceptEdits / plan / auto / dontAsk / bypassPermissions. Only the first three are in the base Shift+Tab cycle; auto and bypassPermissions join it conditionally and dontAsk is set only by flag or settings¹ - walked through in the modes ladder on-request / never / granular (untrusted was removed) allow / ask / deny Auto-review / Allowlist / Run Everything default / autoApprove / autopilot None - YOLO by default; gates come from a container or extension
Org/managed override Yes (managed settings.json) Yes (managed config) Yes (managed config) Yes (Team / Enterprise) Yes (Business / Enterprise; content exclusions, MCP policy) -
Evaluation order Deny > Ask > Allow Sandbox blocks first, then approval gate Last match wins Allowlist, then sandbox where supported, then the classifier² Per-call gate unless auto-approve set N/A
Per-agent profile No (single profile per session) No (single mode per session) Yes (per primary agent) - - -
“YOLO mode” bypassPermissions mode (--dangerously-skip-permissions) danger-full-access + never All tools allow “Run Everything” chat.tools.global.autoApprove The default posture

¹ auto appears in the cycle when your account and model qualify for it; bypassPermissions only after you start the session with an enabling flag, or set permissions.defaultMode: "bypassPermissions" in user or managed settings (project and local settings are ignored). On terminal and VS Code sessions from v2.1.283, auto is also the starting mode where it is available; claude -p starts in Manual. auto is worth knowing as its own category: rather than a fixed rule list, a separate classifier model reviews each action and blocks escalations, which is a different failure mode from a pattern match - it can be wrong in both directions, and Anthropic’s own documentation says it reduces prompts without guaranteeing safety.

² Cursor states plainly that Auto-review is not a security boundary - the classifier can allow a call you would have blocked, or block one you would have allowed. The sandbox is the part that actually holds a line.

Tool How
Claude Code Add allow rules for the tool pattern, or move Bash(...) out of ask.
Codex --ask-for-approval never (or approval_policy = "never") and pick a sandbox you trust, since autonomy is still limited by the sandbox mode. The flag and config surface have moved across releases, so confirm against your installed version.
OpenCode Set the relevant tool to allow on the active primary agent.
Cursor Set the Run Mode to “Auto-review” (the recommended default since Cursor 3.6), or to “Allowlist” with sandboxing enabled for a deterministic list (or “Run Everything,” with the usual warning).
Copilot Set chat.permissions.default to autoApprove, or flip specific chat.tools.*.autoApprove keys.
  • “Sandbox” is a real OS-level isolation tier in Codex. Claude Code has one too, but it covers shell commands only and is off by default; Read, Edit and Write follow permission rules instead. In OpenCode the word still means a container or other external isolation.
  • “Approval” in Codex is the frequency policy (ask/never); in Claude Code it’s the result of an ask rule. Same word, different layer.
  • Codex’s on-failure approval mode is deprecated and untrusted was removed as an approval policy - don’t recommend either. trust_level = "untrusted" on a project is a different setting.

You don’t have to design the whole policy up front, and nobody does. Start from your tool’s default posture and let the next session tell you what to change: the command you approved three times without reading becomes an allow rule, the one that made you flinch becomes an ask, and the handful you can name in advance as never-acceptable become deny. That last group is the only part worth writing before you’ve felt the friction, because it’s the only part that has to hold when you’re not watching.