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 repoFine, every time
Run the test suiteFine, every time
Edit your codeUsually fine
Run a one-off shell commandDepends entirely on the command
Push to a remoteTell me first
chmod a system fileNo

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 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.

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 forNot
Decide broadly what the agent can do unattendedPermissionsHooks
Block one specific command pattern with logic (regex against args, conditional logic)Hook on PreToolUsePermissions alone
Take the agent fully read-only for one sessionPlan modeToggling every permission
Restrict a worker subagent’s toolsSubagent tools: fieldSession-wide permissions
Enforce a rule across the whole orgManaged/policy configPer-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.”

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.

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.

AspectClaude CodeCodexOpenCodeCursorCopilotPi
Primary axisPermission mode + allow/ask/deny rules per toolSandbox tier × approval policyPer-tool per-agentIDE run mode plus CLI project permissionsPer-call approval + auto-approve knobsTrust gate on project resources; no per-tool permission popups
OS-level sandboxNoYes (read-only / workspace-write / danger-full-access)No (wrap in a container if you need it)Yes (Seatbelt on macOS, Cursor v2.0+) - a layer on top of the run modes, not a mode of its ownOptional (chat.agent.sandbox.enabled, macOS/Linux)Via container/extension, not built-in
Approval modesNamed 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 flag-only¹ - walked through in the modes ladderuntrusted / on-request / neverallow / ask / denyAuto-review / Allowlist / Run Everythingdefault / autoApprove / autopilotNone - YOLO by default; gates come from a container or extension
Org/managed overrideYes (managed settings.json)Yes (managed config)Yes (managed config)Yes (Team / Enterprise)Yes (Business / Enterprise; content exclusions, MCP policy)-
Evaluation orderDeny > Ask > AllowSandbox blocks first, then approval gateLast match winsAllowlist, then sandbox where supported, then the classifier²Per-call gate unless auto-approve setN/A
Per-agent profileNo (single profile per session)No (single mode per session)Yes (per primary agent)---
“YOLO mode”bypassPermissions mode (--dangerously-skip-permissions)danger-full-access + neverAll tools allow”Run Everything”chat.tools.global.autoApproveThe 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. 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.

ToolHow
Claude CodeAdd allow rules for the tool pattern, or move Bash(...) out of ask.
Codex--ask-for-approval never (and pick a sandbox you trust) - the exact flag and config surface has moved across recent Codex releases, so confirm against your installed version.
OpenCodeSet the relevant tool to allow on the active primary agent.
CursorSwitch auto-run mode to “Allowlist” with sandboxing enabled (or “Run Everything,” with the usual warning).
CopilotSet chat.permissions.default to autoApprove, or flip specific chat.tools.*.autoApprove keys.
  • “Sandbox” is a real OS-level isolation tier in Codex; in Claude Code and OpenCode the word is used loosely (or refers to Docker).
  • “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 - don’t recommend it.

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.