[BUG] PreToolUse hook `permissionDecision: "ask"` is overridden by `auto` mode for benign `dangerouslyDisableSandbox` commands (contradicts documented hook precedence)
Summary
In auto permission mode, a PreToolUse hook that returns permissionDecision: "ask" for a Bash call with dangerouslyDisableSandbox: true is silently overridden to auto-allow when a background safety classifier judges the command benign. No permission prompt appears and the command runs outside the sandbox with no user review, even though the hook explicitly returned ask. Some commands (a destructive verb like rm, or reading a system file such as /etc/hosts) do surface the hook's ask, so the classifier — not the hook — is deciding whether the escape is reviewed. The classifier's criteria are opaque and not simply about filesystem scope: a bare echo runs silently, but so does gh issue view … --json — a command that reads a GitHub token from the macOS Keychain and makes a network call, both outside the project. This contradicts the documented guarantee that a hook's permissionDecision takes precedence over the permission mode "regardless of the mode", and it means a user-authored safety hook cannot reliably force review of a sandbox escape — including escapes that read credentials and reach the network.
Environment
- Claude Code version:
2.1.201 - Platform: macOS (Darwin 24.6.0), Apple Silicon
- Model: Claude Opus 4.8
- Permission mode:
auto(permissions.defaultMode: "auto") - Sandbox: enabled, with
allowUnsandboxedCommands: true
Reproduction
1. Settings (.claude/settings.json) — auto mode plus a PreToolUse hook on Bash:
{
"permissions": { "defaultMode": "auto" },
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [
{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/ask-on-unsandboxed.sh\"" }
] }
]
},
"sandbox": { "enabled": true, "allowUnsandboxedCommands": true }
}
2. Hook (.claude/hooks/ask-on-unsandboxed.sh) — return ask for any unsandboxed command:
#!/usr/bin/env bash
val="$(jq -r '.tool_input.dangerouslyDisableSandbox // false')"
if [ "$val" = "true" ]; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:"Runs OUTSIDE the sandbox — review before allowing."}}'
fi
exit 0
3. Have the agent run three Bash calls, each with dangerouslyDisableSandbox: true:
| Probe | Command (with dangerouslyDisableSandbox: true) | Prompt shown in auto? |
| --- | --- | --- |
| A | echo "hi" — no filesystem or other side effects | No — runs silently |
| B | rm -f "$PROJECT/nonexistent" — destructive verb, target inside the project | Yes |
| C | cat /etc/hosts — read a path outside the project | Yes |
| D | gh issue view N --repo … --json … — reads a Keychain token + network call, outside the project | No — runs silently |
The hook returns ask for all four (verified: it fires and emits the JSON in every case). Only probes B and C actually prompt; probes A and D are auto-approved and execute unsandboxed with no prompt. Probe D is the most concerning: a command that reads credentials and makes a network request is classified benign and escapes the sandbox unreviewed, despite the hook's ask. The gate is clearly a semantic classifier rather than a filesystem-scope rule — C (a local read of a public system file) prompts, while D (credentials + network) does not.
Observed vs. expected
- Observed: Probe A runs outside the sandbox with no permission prompt; the hook's
askis discarded. - Expected: Per the documented precedence, the hook's
askshould surface a prompt for every unsandboxed command in every mode, includingauto.
Documentation this contradicts
Hook decisions take precedence over the mode — hooks.md:
Your hook's permissionDecision takes precedence over the user's permission mode setting, allowing you to enforce stricter policies regardless of the mode.
The documented precedence chain (permissions.md) is: deny/ask rules > hook decision > permission mode. There are no ask/deny rules matching these probes, so the hook's ask should beat auto mode. It doesn't for probe A.
Unsandboxed escapes require approval — sandboxing.md:
The retried command runs outside the sandbox, so it goes through the regular permission flow and requires your approval.
Probe A ran outside the sandbox with no approval. (auto is documented as "auto-approves tool calls with background safety checks", so one might argue the classifier satisfies this clause — but that cannot excuse the hook-precedence violation above.)
Undocumented gap: none of hooks.md, permissions.md, or sandboxing.md describes how a PreToolUse hook interacts with the dangerouslyDisableSandbox retry path. The observed behavior (classifier gating the escape approval and overriding the hook) is unspecified.
Additional notes
- A hook
denyis honored on the escape path inautomode (the command is blocked), and hookaskis honored for a normal sandboxed command inautomode (it prompts). Onlyaskon thedangerouslyDisableSandboxpath is subject to the classifier. So the failure is specific to the intersection ofask+ escape path +automode. - Impact is safety-reducing: a user-authored hook intended to force review of every sandbox escape cannot do so; inert-looking commands escape the sandbox unreviewed. The only reliable workaround is to return
deny(blocking the escape hatch entirely) or setsandbox.allowUnsandboxedCommands: false.
Related issues
- #42797 — Auto-mode ignores
permissions.ask(settings rules, not hooks; no sandbox angle) — same spirit, different mechanism. - #51798 — hook
allowno longer suppresses the unsandboxed prompt (the inverse direction on the same path; closed as not planned). - #43713 —
autoAllowBashIfSandboxedbypassed for shell expansions (confirms a classifier gates sandbox-related auto-approval). - #39344 — hook
asksilently overridespermissions.deny(hookaskinteracting wrongly with the permission system).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗