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 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, 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”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.
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.
Cursor documents an Agent Sandbox for macOS in the course’s target release. Keep this claim platform-scoped: the public material checked here does not establish equivalent Linux/Windows behavior, so do not generalize the macOS statement.
Three run modes, set in Cursor Settings or via /run-everything in the CLI (/auto-run is an alias):
- Auto-review (default since Cursor 3.6) - allowlisted calls run immediately, other shell commands run in the sandbox when possible, and anything left over goes to the Auto-review classifier.
- Allowlist - actions on your allowlist run without approval. Sandboxing is a layer within this mode (folded in as of Cursor 3.5, no longer a separate mode) - with it enabled, supported shell commands can also run in the sandbox.
- Run Everything - the historical “YOLO mode,” no gating.
There are command allowlist and denylist fields for fine-grained control. Two caveats worth flagging honestly: community reports note that sandboxed Allowlist runs can silently bypass the explicit allowlist in some builds, and the denylist has historically been bypassable via && chaining. Treat the allowlist/denylist as advisory and the sandbox as the load-bearing control.
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. The one gate Pi has is project trust: the first time you open a project, Pi asks whether to trust it before loading project-local .pi/ config, extensions, and skills. That’s about whether to run code the project supplies, not about approving individual tool calls during a session. Trust decisions persist in ~/.pi/agent/trust.json; -a/--approve and -na/--no-approve set it for a single run.
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 | Auto-run mode + allow/denylist | 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 (Agent Sandbox: macOS/Linux native, Windows via WSL2) - a layer inside Allowlist mode, not a mode of its own | Optional (chat.agent.sandbox.enabled, macOS/Linux) | Via container/extension, not built-in |
| Approval modes | Named modes: default / acceptEdits / plan / dontAsk / bypassPermissions (Shift+Tab to cycle), over allow/ask/deny rules - 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 | Sandbox first, then allow/denylist (with caveats) | 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 |
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.