# Which Coding Agent Has the Best Native Controls? (August 2026)

Approval modes, policy rules, sandboxing, hook coverage, audit logs, escape hatches, and unattended behavior — compared across Claude Code, Codex CLI, Cursor, dsh, Muse Code, Grok Build, Antigravity, Goose, opencode, and more.

# Which coding agent has the best native controls?

Every harness now ships *something* — approval prompts, an allowlist, a sandbox, hooks. None ships everything, the defaults differ wildly, and the docs rarely say what happens in the cases that matter (approval times out; agent runs headless; someone passes the yolo flag). This page compares the native control surfaces of the major harnesses as of August 2026, judged on seven questions:

1. **Approvals** — is there a native ask, and what are its modes?
2. **Rules** — can you write allow/deny policy, and how expressive is it?
3. **Sandbox** — is there OS-level isolation, and is it on by default?
4. **Interception** — can a third party stand on the call path, and does it see *every* tool call?
5. **Audit** — what record exists that the agent didn't author?
6. **Escape hatch** — what single flag disables the controls?
7. **Empty chair** — what happens to an approval when nobody is there to answer it?

This survey is the per-harness data behind [our harness control patterns page](/harness-control-patterns), which names the eight control mechanisms these columns instantiate. It is also the native-controls companion to our [coverage matrix](/coverage) (which documents what *our* layer intercepts per harness); the same survey for the other side of the wire is [the MCP server controls comparison](/mcp-controls). Deep pages per harness are linked in each row as we publish them. Corrections welcome — every claim links to its source, and this page changes when the harnesses do.

## The short version

- **Strongest defaults: Muse Code and DeepSeek Harness.** Muse Code ships an OS sandbox (Seatbelt/bubblewrap) and an append-only event log, both on by default — with the documented caveat that hooks and MCP servers run *outside* its sandbox ([the deep page](/controls/muse-code) has the full model). dsh defaults its native sandbox to **read-only**, fails unanswerable approvals closed, and keeps an append-only session log with a runtime-enforced invariant: *model-visible means logged*.
- **Most mature rule system: Claude Code.** Layered settings with managed (org) scope, pattern rules, hooks on every call, and now a classifier-based [auto mode](/blog/claude-code-auto-mode-wrote-a-policy) that derives a trust profile from your own usage.
- **Cleanest architecture: DeepSeek Harness.** One pipeline, typed interception points covering everything including MCP tools and Code Mode sub-calls, the only native *ask-denies-itself* headless posture, and subagents whose prompts auto-reject by default. Core ships architecture, not rules — [the deep page](/controls/dsh) explains the trade.
- **Most expressive rules, least available: Gemini CLI.** Its TOML policy engine (per-mode rules, five priority tiers, root-owned admin policy files, and `ask_user` resolving to **deny** in non-interactive mode) was the high-water mark for rule design — Google retired it with the tool in June 2026. Its successor [Antigravity](/controls/antigravity) ships a simpler `action(target)` permissions engine, and the only hook vocabulary with a native `ask`.
- **Best approval hardening: OpenClaw** — approvals bind exact command/cwd, approved scripts bind to on-disk snapshots, inline-interpreter forms force approval, and prompts detect invisible-Unicode obfuscation. Also **the most permissive default posture**: exec `security: "full"`, `ask: "off"`, on an agent built to run unattended. Both facts on [the deep page](/controls/openclaw).
- **The honest zero: pi.** Mario Zechner and Armin Ronacher's ~80k-star minimal harness ships four tools and **no permission system at all** — deliberately, documented, with containment patterns (micro-VM extension, Docker, OpenShell) offered instead. It's the cleanest possible statement that in a minimal-core design, control is an *extension* — which also means the extension seam is where control has to arrive. Its hard fork **Prime Agent** (Prime Intellect, ~18k stars) carries the same zero into always-on autonomous territory — one ipython kernel, daemon-resident sessions, and an inherited `hasUI` signal that misreports headless runs ([deep page](/controls/prime-agent)).
- **Most dangerous defaults elsewhere: Amp and Cline.** Amp defaults to no approval prompts; Cline's yolo is a literal approve-everything, and its "safe commands" tier trusts the model's *own* `requires_approval` self-classification — the classifier is the thing being governed.

## The comparison

<div class="acp-post-wide-table" markdown="1">

| Harness | Approvals | Rules | Sandbox | Interception | Native audit | Escape hatch | Empty chair |
|---|---|---|---|---|---|---|---|
| **Claude Code** ([deep page](/controls/claude-code)) | Six modes incl. [`auto` — classifier-screened, default for Pro/Max/Team since 08-14](/blog/claude-code-auto-mode-wrote-a-policy) | `permissions.allow/ask/deny` patterns; user + project + local + **managed** scopes; deny at any scope wins; `autoMode.*` prose rules | Available, off by default (Seatbelt / bubblewrap; credential masking) | Hooks on **every** tool call; deny overrides allow; fail-open on timeout | JSONL transcripts + opt-in OTel `tool_decision` (decision + source) | `--dangerously-skip-permissions` — but deny rules, hooks, and an `rm -rf` circuit breaker still hold; refuses root | **Defined**: `dontAsk` mode auto-denies; unresolvable headless asks deny and continue |
| **Codex CLI** ([deep page](/controls/codex-cli)) | `untrusted` / `on-request` / `never` (`on-failure` removed 0.143) + **Guardian reviewer agent** (`--approve-for-me`, fail-closed, 0.147) | Layered TOML + permission profiles (path→read/write/deny maps, network domains) + enterprise `requirements.toml`; `approved_with_amendment` persists approvals as reviewable rules | Native on all three OSes — Seatbelt / bubblewrap+seccomp / **native Windows sandbox**; workspace-write networks off by default | **Full hooks system, on by default** — PreToolUse / PermissionRequest / PostToolUse, Claude Code-compatible schema, `/import` migrates ([hooks reference](/blog/codex-cli-hooks-reference)) | Session rollouts (JSONL + state DB); `--ephemeral` and `codex delete` erase | `--yolo` (sandbox + approvals both off) | **Defined**: `exec` never prompts — blocked actions fail back to the model |
| **Cursor / Cursor CLI** ([deep page](/controls/cursor)) | Run Modes: **Auto-review** (allowlist → sandbox → LLM classifier; recommended default), Allowlist, Run Everything | [`permissions.json`](/controls/cursor#permissions-json) takes deterministic `terminalAllowlist` / `mcpAllowlist` patterns plus **natural-language** `autoRun` allow/block instructions; allowlist officially "best-effort, not a security boundary" (3 bypass CVEs); `.cursor/rules` are steering, not enforcement | Seatbelt / Landlock+seccomp (WSL2 on Windows); network blocked by default; `sandbox.json` | Rich hook events; `ask` honored only for shell/MCP events; **`failClosed` defaults to false** | **None on any tier** — DIY via hooks | Run Everything mode; CLI `--force` (alias `--yolo`) | Cloud agents skip Run Modes entirely — **auto-run inside a VM** |
| **DeepSeek Harness (dsh)** ([deep page](/controls/dsh)) | Approval service, mounted per composition | None shipped — policy arrives as a plugin | **Native, default `read-only`** (`ctx.sandbox`; also `workspace-write`, `danger-full-access`); bwrap-compatible local runner, remote E2B backend exists | Typed `tools/pre-execute` / `post-execute` on **one pipeline covering everything**, incl. Code Mode sub-calls; post can rewrite output | Trajectory log | `danger-full-access` preset (sandbox open, approval `never`) — named, visible profile choice | **Ask resolves to deny, natively** |
| **Muse Code** (Meta) ([deep page](/controls/muse-code) &middot; [ACP install guide](/integrations/muse-code)) | On by default + **LLM approval judge** (`--approval-judge off` routes all to human); staged review of compound shell commands; workspace trust prompt | `on-request` / `untrusted` / `never` modes; saved prefix rules per workspace root; **deny always beats allow**; interpreter prefixes can't be broad-allowed | **Seatbelt / bubblewrap, on by default**; refuses shell if sandbox unverifiable; `.git`/`.muse`/`.agents` read-only even in-workspace; network default `proxy-only` (per-destination review) | Hooks on the full lifecycle incl. model calls, permission requests, subagent events; **fleet-managed hooks** via `managed_hooks_path` (pre-approved) — caveat: hooks + MCP run **outside** the sandbox | **Append-only local event log** of every model call, tool run, approval, edit | `--yolo` (approval + sandbox off, **trusts the workspace** — loads the checkout's `AGENTS.md`); `--disable-sandbox` also unconfines file tools + forces full network egress | Not documented |
| **Grok Build** (xAI) ([deep page](/controls/grok-build)) | `default` / `auto` (classifier) / `plan` / always-approve / `dontAsk` (headless, **fail-closed**) | TOML `[permission]` rules (`action`/`tool`/`pattern`), deny always wins; explicit allow auto-approves | 5 profiles (`off` default → `strict`), Landlock/Seatbelt; child-network block Linux-only | **PreToolUse hooks fire in every mode incl. always-approve**; loads hooks from `~/.claude/settings.json` but **doesn't parse Claude's output vocab**; `updatedInput` rewrite; HTTP hooks; **fail-open core (5s timeout)** | Session log (`~/.grok/sessions`, format undocumented) | `--always-approve` / `--yolo` (deny rules + hooks still fire) | `dontAsk`: silent deny without an allow rule |
| **Antigravity** (Google, absorbed Gemini CLI) ([deep page](/controls/antigravity)) | TUI approval cards; `accept-edits` / `plan` modes; headless (`-p`) **soft-denies** unobtainable approvals (exit 0 + stderr notice naming the needed rule) | `permissions.allow/ask/deny` `action(target)` rules in settings.json — files, commands (prefix/regex), URLs, browser actuation, `mcp(server/tool)`; **deny > ask > allow**; workspace files auto-allowed, everything else defaults to ask | `enableTerminalSandbox` (off by default; nsjail / sandbox-exec / AppContainer); per-prompt opt-in/out; `unsandboxed()` rule escape | `hooks.json` hooks, 5 events, regex matchers; **native `ask`/`force_ask` in the hook vocabulary** — the only harness with a first-class hook-issued ask; **fail-closed on hook failure** | Per-conversation transcript JSONL; enterprise tier: central audit logging, MCP restrictions, spend caps (Gemini Enterprise) | `--dangerously-skip-permissions` — **total, verified**: hooks are not invoked at all under the flag (unlike Claude Code / Grok Build, where denies + hooks survive bypass) | **Defined**: headless soft-deny, run continues |
| **Goose** (Block / LF) | `auto` / `smart_approve` (LLM judge + `read_only_hint`) / `approve` / `chat` | Extension allowlist via `GOOSE_ALLOWLIST` URL; ~35 danger patterns exist but are **off by default and prompt-only** | None | **None** — extensibility is MCP-shaped, no tool-call hook | — | `GOOSE_MODE=auto` (the default) | Undefined |
| **opencode** ([deep page](/controls/opencode) · [permissions reference](/blog/opencode-permissions-reference)) | Native ask via `permission` config | `allow`/`ask`/`deny`, last-match-wins, per-tool + bash patterns (`"rm *": "deny"`) | None | JS/TS plugin hooks incl. the permission system (reliability caveats reported) | Session log | `--auto` (denies still enforced) | Undefined |
| **Hermes Agent** ([deep page](/controls/hermes)) | Inline prompt on ~47 dangerous patterns | **Published blocklist**: 12 hardline patterns that hold in every mode, quoting-hardened ([our read](/blog/what-hermes-blocklist-taught-us)) | None | `pre_tool_call` / `post_tool_call` on **every** tool, in-process; hook API is deny-only, post is observational | Session log | yolo mode (hardline tier survives it) | Prompt with no answerer — halt |
| **Amp** (Sourcegraph) | **Default: no approval prompts** | `amp.permissions` (`allow`/`ask`/`reject`, glob matches, per-thread/subagent context); `amp.guardedFiles.allowlist` | None | Plugin API | — | `amp.dangerouslyAllowAll` | Undefined |
| **Cline** ([guide](/integrations/cline)) | Per-category auto-approve toggles | Safe-command classification | None | Hooks via stdin (open bug: hooks execute even when permissions disabled) | — | **CLI default: auto-approve ON** | Auto-approve |
| **Crush** (Charm) | Permission service on side-effectful calls | `permissions.allowed_tools`; `options.disabled_tools` | None | No hook system | — | `--yolo` (still hard-blocks ssh/scp/rsync) | Undefined |
| **OpenHands** | Confirmation mode default-on; `--llm-approve` risk analyzer | ConfirmRisky policy, configurable threshold | Sandboxed runtime | SDK-level pluggable `SecurityAnalyzer` + `ConfirmationPolicy` | — | `--always-approve` / `--yolo` | Undefined |
| **Qwen Code** (Alibaba) ([deep page](/controls/qwen-code)) | Five modes: `plan` / `default` / `auto-edit` / **`auto` (default: an LLM classifier approves "safe" actions)** / `yolo` | Claude-grammar `permissions.allow` / `ask` / `deny` rules (`Bash(git *)`, `Read(./secrets/**)`, `mcp__x`), deny > ask > allow; system settings file overrides user and project | Optional: Docker/Podman via `tools.sandbox`, macOS seatbelt profile per project | Hooks are Claude Code's contract: `PreToolUse` / `PostToolUse` command hooks, `permissionDecision` allow/ask/deny, exit 2 blocks; timeout in ms; `disableAllHooks` off switch | — | `--yolo` (hooks still fire) | **Documented**: a hook `ask` degrades to `deny` in headless runs and background subagents |
| **pi** (earendil-works) ([deep page](/controls/pi)) | **None — by design.** No permission prompts of any kind | **None.** "Pi does not include a built-in permission system" — the docs say so plainly and point you at containment | None built in; documented patterns: Gondolin micro-VM extension, Docker, OpenShell | TypeScript extensions can mediate tool execution (Gondolin reroutes the built-in tools; hook API undocumented) | — | The default *is* full access | N/A — there is nothing to resolve |
| **Prime Agent** (Prime Intellect) ([deep page](/controls/prime-agent)) | **None — by design** (pi fork; kept the honest zero) | **None.** Extension docs list the permission gate as the canonical extension to write | None built in; one `ipython` kernel runs everything with the launching user's access | pi's typed events kept intact: `tool_call` (blockable, input-mutable, **fails closed on handler error**) + `tool_result` (rewritable); no MCP second path (MCP = Python skills inside ipython) | Branchable session tree (the run's own account) | The default *is* full access; `/autonomous` limits bound spend, not actions | **`ctx.hasUI` lies in print mode** (reports true headless, 0.8.x; all CLI sessions run in daemon workers) — the no-op UI's unanswerable confirm is the reliable empty-chair signal |
| **fx** (Vercel Labs) ([deep page](/controls/fx)) | `ask` / `auto` (default) / `yolo`; `auto` = rules first, then a **hardcoded model review** of unresolved sensitive calls (docs say `openai/gpt-5.4`; launch-day source hardcodes `zai/glm-5.2` — not configurable either way), human fallback when interactive | Wildcard `permission` JSON, user-global + per-workspace, last match wins; `/permissions remember` stores exact confirmed rules; **repo `.fx.json` can't set rules or model** | `os` mode (macOS only: workspace-scoped writes), **default `none`**; widening access is a separate approval | **`fx acp`** — as an Agent Client Protocol server, fx sends every approval-gated call to the client (`session/request_permission`, full raw arguments) and **fails closed**; no hooks/plugin surface otherwise | — | `yolo` (permission checks + sandbox off; requires `yolo_acknowledged`; `FX_PERMISSION_MODE=yolo` env does the same) | Undefined — the reviewer's human fallback is documented for interactive mode only |

</div>

Sources: each harness's official docs, linked from its deep page or row; adoption and behavior claims for harnesses we haven't integrated come from vendor documentation and are marked where uncertain. Kilo (Cline lineage) and Mistral Vibe track their parents' models and are omitted from the table until we've verified their divergences (Qwen Code, also Gemini CLI lineage, is in the table since we verified its hook contract and headless posture on 2026-09-03); Devin CLI ships JSON hooks in `.devin/` and reads `.claude/` hook directories. Aider remains git-diff-review by design — with one elegant exception worth stealing: under `--yes-always`, shell execution is auto-*denied*, not auto-approved. This table extends [our July primary-source read of eleven harnesses' guardrails](/blog/what-survives-yolo-mode), where the escape-hatch column is examined in depth.

## What the table actually says

**Interception is the dividing line.** Read the Interception column and the ecosystem splits in two: harnesses where a third party can stand on the call path and see everything (Claude Code, Codex as of its 2026 hooks system, dsh, Hermes, Muse Code, Grok Build, Cursor), and harnesses where it can't (Goose and Crush, principally). Every other control can be added from outside if interception is complete; nothing can be added if it isn't. If you're choosing a harness with control in mind, this column outweighs the rest of the table.

**Convergence on Claude Code's hook contract.** Codex's new hooks are *deliberately* Claude Code-compatible and `codex /import` migrates Claude Code configs wholesale; Cursor and Hermes honor exit-code-2 blocking and the JSON decision shapes; Grok Build loads hooks straight out of `.claude/settings.json` (but parses only its own top-level decision vocabulary — [the trap](/controls/grok-build)); Devin CLI auto-discovers `.claude/` hooks; dsh ships a `hooks-claude-code` bridge. The PreToolUse contract is becoming the ecosystem's POSIX — which means a control layer built against it ports cheaply, and so does a permission-model bug (the same settings-format bypass has been reproduced in two different harnesses).

**Classifiers took over the approval loop this season — ledgers didn't follow.** All three majors now put a model where the human prompt was: Claude Code's auto mode (default-on for paid personal accounts since August 14), Codex's Guardian reviewer (0.147), Cursor's Auto-review (3.6). Hermes made LLM `smart` approvals its default in July; Grok Build, Goose, and OpenHands have their own variants; Cline goes furthest and trusts the governed model's *own* risk flag. The fatigue math is real — Anthropic's published numbers have humans catching 13.6% of dangerous commands at the prompt versus 89% for the classifier. But exactly two harnesses (dsh, Muse Code) added an independent append-only record of what ran, and none added a queryable trail of what their classifier decided or why. The ecosystem is spending fast on making attended sessions quieter and almost nothing on making decisions explainable afterwards.

**The empty chair is being answered — by the newest designs.** dsh resolves unanswerable asks to deny; Codex `exec` never prompts and fails blocked actions back to the model; Claude Code ships `dontAsk`; Gemini CLI's policy engine resolves `ask_user` to deny non-interactively; Hermes denies flagged commands in cron and one-shot modes. That's real convergence on the right answer, and it's recent. The counterexamples are the ones to check before unattended use: Cline's yolo, Cursor's cloud agents (auto-run inside a VM), and OpenClaw's default posture on an agent designed to run unattended. The reasoning is in [the empty-chair test](/blog/interactive-vs-autonomous-the-empty-chair-test).

## Where every native model ends

Grade any row generously and the same four gaps remain, because they're structural rather than missing features:

1. **The record is self-authored.** Transcripts and trajectories are written by the process they describe, on the machine that ran it. Only Muse Code hardens this locally; nothing ships it off-machine.
2. **Policy is per-machine.** Settings files, profiles, and config live with each install. Two engineers on one codebase run whatever their machines accumulated. Managed settings (Claude Code) and fleet-managed hooks (Muse Code) are the first cracks in this, for those two harnesses, org-wide.
3. **One flag undoes it.** Nearly every row has an escape hatch that disables the controls *and* the record of the controls being disabled.
4. **Rules don't travel.** A deny you wrote in `settings.json` doesn't exist in `config.toml`, `crush.json`, or `permission.yaml`. Every harness added means re-deriving your policy in a new dialect — or [carrying one policy across all of them](/coverage).

Those four are the job of a layer above the harness. The native controls are genuinely good now, and getting better fast — this page exists because choosing well matters. What none of them try to be is the same policy, enforced identically, with an independent record, on every machine and every harness you run. That's [the layer we build](/); the per-harness deep pages linked above say precisely where each handoff happens.

## Frequently asked questions


