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 truly dangerous things can’t happen at all, even if the agent decides they’re a good idea.

Every CLI in scope encodes exactly that three-way decision - do it without asking, ask me first, or refuse outright - on every action the agent attempts. Same job, then: decide what the agent can do unattended.

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.

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 beats user beats project. An org admin can deny tool patterns globally; project settings can broaden within those limits.

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-agentAuto-run mode + allow/denylistPer-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 (Agent Sandbox: macOS/Linux native, Windows via WSL2) - a layer inside Allowlist mode, not a mode of its ownOptional (chat.agent.sandbox.enabled, macOS/Linux)Via container/extension, not built-in
Approval modesNamed modes: default / acceptEdits / plan / dontAsk / bypassPermissions (Shift+Tab to cycle), over allow/ask/deny rules - 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 winsSandbox first, then allow/denylist (with caveats)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
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.