[BUG] PreToolUse hook `permissionDecision: "ask"` is overridden by `auto` mode for benign `dangerouslyDisableSandbox` commands (contradicts documented hook precedence)

Resolved 💬 2 comments Opened Jul 12, 2026 by srseil Closed Jul 14, 2026

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 ask is discarded.
  • Expected: Per the documented precedence, the hook's ask should surface a prompt for every unsandboxed command in every mode, including auto.

Documentation this contradicts

Hook decisions take precedence over the modehooks.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 approvalsandboxing.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 deny is honored on the escape path in auto mode (the command is blocked), and hook ask is honored for a normal sandboxed command in auto mode (it prompts). Only ask on the dangerouslyDisableSandbox path is subject to the classifier. So the failure is specific to the intersection of ask + escape path + auto mode.
  • 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 set sandbox.allowUnsandboxedCommands: false.

Related issues

  • #42797 — Auto-mode ignores permissions.ask (settings rules, not hooks; no sandbox angle) — same spirit, different mechanism.
  • #51798 — hook allow no longer suppresses the unsandboxed prompt (the inverse direction on the same path; closed as not planned).
  • #43713 — autoAllowBashIfSandboxed bypassed for shell expansions (confirms a classifier gates sandbox-related auto-approval).
  • #39344 — hook ask silently overrides permissions.deny (hook ask interacting wrongly with the permission system).

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗