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, anygit rebase, deletions, anything touchingnode_modulesorpackage-lock.json,npm install/pip install. - Always deny -
rm -rf, edits to.envor secrets, writes outside the project root,sudo,chmod, pushing tomaindirectly. - Scoped allow patterns -
Bash(npm run *)allowed,Bash(npm install *)asked. Same tool, different command shapes. - Per-agent profiles (OpenCode) - your
planagent is read-only no matter what; yourbuildagent has full edit access; a customsecurity-reviewagent has read + a narrow shell allowlist. - Org-wide policy - managed config forbids any tool that can touch
prodfor 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.
Will it run?
Section titled “Will it run?”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.
Why this and not…
Section titled “Why this and not…”| 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.
How it works in each tool
Section titled “How it works in each tool”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.
Codex separates what the agent is allowed to touch (sandbox) from when it asks before acting (approval policy). The two combine.
Sandbox modes (--sandbox):
read-only- agent can read but cannot edit files or run commandsworkspace-write- agent can edit files inside the workspace and run sandboxed commandsdanger-full-access- no sandboxing (use deliberately)
Approval policies (--ask-for-approval):
untrusted- ask for every actionon-request- agent decides when to asknever- never ask
on-failure is deprecated.
Common combinations:
- Plan-like behaviour:
--sandbox read-only --ask-for-approval untrusted - YOLO mode:
--sandbox danger-full-access --ask-for-approval never(only in throwaway environments)
Permissions are per-tool per-agent. Each primary agent (build, plan, custom) gets its own permission map:
permission: read: allow edit: ask bash: ask glob: allow grep: allow list: allow task: allowSwitching primary agents (Tab between build and plan) effectively switches permission policies - plan typically has edit: ask or edit: deny while build has edit: allow.
Wildcards * and ? are supported in tool patterns (but not ** recursive globs). Evaluation is last-match-wins - a common pattern is to put the catch-all "*" rule first and put more specific rules after it.
OpenCode itself does not ship a built-in OS-level sandbox; for stronger isolation, run the CLI inside a container (Docker, Incus, etc.) as a wrapper layer.
Three run modes, set under Settings > Agents > Approvals & Execution:
- Auto-review - allowlisted calls run immediately, other shell commands run in the sandbox when possible, and anything left over goes to the Auto-review classifier. The recommended setting for most people.
- Allowlist - actions on your allowlist run without approval, and sandboxing is optional rather than automatic. Deterministic, and worth it when you have a small set of trusted repeat actions.
- Run Everything - the historical “YOLO mode,” no gating.
Sandboxing sits on top of the modes, not inside one. It decides where a supported terminal command runs, not whether the mode consults the classifier - which is why Auto-review both sandboxes and reviews. A sandboxed command reads and writes inside the workspace, is blocked from the network by default, and can’t touch protected paths like .git/config, .git/hooks, or .vscode. Commands needing full system access can’t be sandboxed at all: Cursor flags those and asks you to approve them. Implementation is Seatbelt via sandbox-exec on macOS (Cursor v2.0+).
Keep the surfaces separate. Cursor CLI documents persistent project permissions in <project>/.cursor/cli.json and user defaults in ~/.cursor/cli-config.json; per-run flags such as model and output format are a separate layer. Sandbox configuration controls filesystem and network reach, not the whole approval policy. Do not treat IDE agent approvals, CLI permissions, and sandbox settings as interchangeable. As of 2026-08-07.
The CLI reference uses .cursor/cli.json for project-scoped permissions. Do not copy older permissions.json examples into the CLI or assume an IDE approval setting has the same schema.
That is also the caveat. Cursor’s classifier-based review is not a complete security boundary: it can allow a call you would have blocked, or block one you would have allowed. Treat approval automation as a convenience, and put the decisive boundary in the sandbox, repository policy, CI, or service controls.
Cloud Agents run inside isolated VMs in Cursor’s cloud - full sandbox-by-construction, with the local-machine threat model not applicable. Environment is reproducible from .cursor/environment.json (Dockerfile supported).
VS Code Chat (Agent mode) is the primary surface. Tool approval prompts fire per call by default.
Approval knobs in settings.json:
chat.permissions.default-default | autoApprove | autopilotchat.tools.terminal.autoApprove- terminal commandschat.tools.edits.autoApprove- file editschat.tools.global.autoApprove- global auto-approve (VS Code docs flag this as disabling most safety)
Sandboxing for agent shell commands via chat.agent.sandbox.enabled (macOS/Linux only).
Content exclusions configured org-wide on github.com block Copilot from indexing or reading specified paths across all surfaces - repo-, org-, or enterprise-scoped.
MCP allowlists are policy controls:
chat.mcp.access,chat.mcp.discovery.enabled,chat.mcp.apps.enabledgate which MCP servers can be added- Business/Enterprise admins set org-level MCP policy on github.com
Org-level controls (Business/Enterprise) also cover model allow/deny lists and extension allow/deny.
Coding Agent and Copilot CLI have their own permission models (GitHub Actions sandbox with built-in firewall; --allow-tool / --deny-tool flags respectively) - out of scope here.
No built-in permission popups. Pi is YOLO-by-default: once a session is running, read, write, edit, and bash all execute without an approval prompt. This is a direct consequence of the minimalist design - there’s no per-tool policy engine to configure.
The trust prompt you do see is a different thing. Pi asks for project trust when project-local settings, resources, packages, extensions, or project .agents/skills require it. AGENTS.md and CLAUDE.md context files load regardless of that decision. Trust controls resource loading, not approval of individual tool calls, and it is not a sandbox. Trust decisions persist in ~/.pi/agent/trust.json; non-interactive modes use the configured default instead of showing a prompt. As of 2026-08-07.
How to add real guardrails:
- Containers/sandboxes - run Pi inside Docker or a VM so a bad
bashcall can’t reach the host. - Permission-gate extensions - intercept tool calls via the extension lifecycle hooks (see Hooks) and require confirmation before anything matching a pattern (e.g.
rm -rf, writes outside the repo) proceeds. - Protected-paths extensions - deny edits to specific files or directories outright.
None of this ships turned on. If you want Claude Code- or Codex-style approval prompts, you’re building or installing that behavior, not flipping a setting.
Comparison
Section titled “Comparison”| 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 | No | Yes (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 own | Optional (chat.agent.sandbox.enabled, macOS/Linux) | 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 flag-only¹ - walked through in the modes ladder | untrusted / on-request / never | 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. 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.
Translation: “make it stop asking”
Section titled “Translation: “make it stop asking””| Tool | How |
|---|---|
| Claude Code | Add 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. |
| OpenCode | Set the relevant tool to allow on the active primary agent. |
| Cursor | Switch auto-run mode to “Allowlist” with sandboxing enabled (or “Run Everything,” with the usual warning). |
| Copilot | Set chat.permissions.default to autoApprove, or flip specific chat.tools.*.autoApprove keys. |
Name collisions
Section titled “Name collisions”- “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
askrule. Same word, different layer. - Codex’s
on-failureapproval 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.