[BUG] PreToolUse hook permissionDecision "allow" does not suppress native permission prompt in interactive mode (v2.1.119)

Status Fixed / completed
Reported on v2.1.59
Maintainer reply ✓ Yes — claude[bot]
Activity 6 comments · opened Apr 24, 2026 · closed May 17, 2026
💡 Likely answer: A maintainer (claude[bot], contributor) responded on this thread — see the highlighted reply below.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

A PreToolUse hook (type: "command") that exits with code 0 and emits a well-formed hookSpecificOutput with permissionDecision: "allow" on stdout does not suppress the native Do you want to X? permission prompt in interactive mode.

The hook executes correctly, stdout JSON is parsed successfully by Claude Code (confirmed in debug log with "Successfully parsed and validated hook JSON output"), but the native UI prompt is still shown to the user, requiring manual Yes/No confirmation.

The documented behavior per hooks reference is that permissionDecision: "allow" bypasses the permission system. This appears to be a regression relative to #28812, which was confirmed resolved on v2.1.59.

Impact: programmatic approval workflows (e.g. remote approval UIs, audit-gated tool execution, compliance logging) cannot rely on hooks to gate tool calls without a redundant native confirmation from the user. Hook exit 2 (deny) correctly blocks the tool — only the allow suppression path is broken.

What Should Happen?

When a PreToolUse hook returns hookSpecificOutput with permissionDecision: "allow" and exits 0, Claude Code should execute the tool without displaying the native permission prompt.

This matches:

  • The documented behavior in the hooks reference: "allow: Bypass permission system and allow"
  • The resolved behavior in #28812, confirmed working on v2.1.59
  • The stated SDK semantics: "bypassPermissions: Auto-approves all tool uses without prompts. Hooks still execute and can block operations if needed."

Error Messages/Logs

Hook stdout (captured via hexdump, confirmed valid JSON):
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"<reason>"}}

Debug log excerpt from ~/.claude/debug/<session>.txt:

[DEBUG] Hooks: Checking first line for async: {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"..."}}
[DEBUG] Hooks: Parsed initial response: {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"..."}}
[DEBUG] Successfully parsed and validated hook JSON output
[INFO] [Stall] tool_dispatch_start tool=Write toolUseId=toolu_... permissionDecisionMs=<N>

The "Successfully parsed and validated" line confirms JSON acceptance. However, permissionDecisionMs shows the delay corresponds to the native prompt wait (>60s until user presses Yes), not the hook response time (which was <1s).

When PermissionRequest event is added as a second hook target with identical JSON structure:
Hook PermissionRequest:Write (PermissionRequest) error:
Failed to run: Hook returned incorrect event name: expected 'PermissionRequest' but got 'PreToolUse'

After patching the hook to emit hookEventName dynamically from stdin's hook_event_name field, PermissionRequest parses successfully — but the native prompt still appears.

Steps to Reproduce

  1. Create a minimal "always allow" hook script:
cat > /tmp/fake-allow-hook.sh <<'EOF'
#!/bin/bash
# Discard stdin (hook event payload)
cat > /dev/null
# Emit well-formed allow decision per hooks reference
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"reproduction test"}}'
exit 0
EOF
chmod +x /tmp/fake-allow-hook.sh
  1. Configure Claude Code user settings:
mkdir -p ~/.claude
cat > ~/.claude/settings.json <<'EOF'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|Bash|MultiEdit",
        "hooks": [
          { "type": "command", "command": "/tmp/fake-allow-hook.sh" }
        ]
      }
    ]
  }
}
EOF
  1. Start Claude Code with hooks debug:
claude --debug hooks
  1. In the Claude Code prompt, request a Write operation:
Create a file at /tmp/repro.txt with the content "hello". Use the Write tool.
  1. Observe: the native Do you want to create repro.txt? 1. Yes / 2. Yes, allow all / 3. No prompt appears even though the hook has already returned allow and Claude Code has logged Successfully parsed and validated hook JSON output.
  1. Expected: no native prompt, Write executes directly.

Actual: native prompt shown, execution stalls until manual confirmation.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.59

Claude Code Version

2.1.119 (Claude Code)

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

Environment details

  • Terminal: Konsole (KDE Plasma 6) on NixOS 25.05 — dropdown did not include Konsole as an option
  • Install method: nix profile install github:sadjow/claude-code-nix (self-contained Bun binary, ~180MB, auto-updated hourly from Anthropic releases)
  • Subscription: Claude Max (via organization)
  • Shell: bash (via nix-shell)

Additional testing performed

1. PermissionRequest event test:

Added PermissionRequest as a second hook target with identical JSON structure. Claude Code rejected the payload with:

Hook returned incorrect event name: expected 'PermissionRequest' but got 'PreToolUse'

Patched the hook to emit hookEventName dynamically based on hook_event_name field from stdin. PermissionRequest then parsed successfully (confirmed via "Successfully parsed and validated hook JSON output" in debug log), but the native prompt still appeared.

2. --permission-mode bypassPermissions CLI flag:

When this flag was set, the hook was not invoked at all — no hook execution visible in debug log, no stdin consumed by the hook script. This contradicts the documented SDK behavior: "bypassPermissions: Auto-approves all tool uses without prompts. Hooks still execute and can block operations if needed." (SDK permissions docs).

Not filing this separately since it may be the same root cause — the hook system and the permission prompt suppression appear to be decoupled in 2.1.119.

3. Fresh state reproduction:

Reproduced on a clean ~/.claude/ (single settings.json, no prior hooks, no plugins, no MCP servers loaded). Bug is not state-dependent.

Related issues

  • #28812 — confirmed working on v2.1.59; possible regression introduced between 2.1.59 and 2.1.119
  • #41615 — similar symptom scoped to .claude/ sensitive paths; this issue is broader (all Write/Edit/Bash/MultiEdit operations)
  • #15897updatedInput field ignored on v2.0.76 (separate but related hook output parsing issue)
  • #13339 — VS Code extension ignores permissionDecision: "ask" (different decision value, similar UI suppression gap)

Expected troubleshooting if helpful

Happy to provide:

  • Full ~/.claude/debug/<session>.txt log (redacted for paths)
  • Additional test matrix across hook event types (PreToolUse, PermissionRequest)
  • Test with various matcher patterns (empty, explicit, regex)
  • Sample hook in other languages (Python, Rust) confirming same behavior

View original on GitHub ↗

6 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/51798
  2. https://github.com/anthropics/claude-code/issues/36059
  3. https://github.com/anthropics/claude-code/issues/41615

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

DarkAngel-agents · 4 months ago

Thank you for the duplicate suggestions. After reviewing each, I believe this issue is distinct — none of the three match the exact scenario here:

#51798 — Specific to Bash with dangerouslyDisableSandbox: true. My reproduction uses the Write tool with no sandbox flags at all.

#36059 — Concerns permissionDecision no longer overriding ask rules. My settings.json has no ask rules configured; permissions.allow only lists read-only operations (Read, Glob, Grep, WebFetch for docs domains). Write/Edit/Bash are not in any allow or ask list.

#41615 — Specific to ~/.claude/ sensitive-file paths. My target path is /tmp/argos-pas5-sandbox/repro.txt — a non-sensitive, non-home location.

The distinguishing characteristic of this issue is that permissionDecision: "allow" is not suppressing the native prompt for plain Write on ordinary paths, under vanilla settings, with no sandbox flags, no ask/allow rules covering the tool, and no sensitive-path logic involved. The hook is invoked, the JSON is parsed and validated (confirmed in debug log), yet the prompt still appears.

Also worth noting: #28812 explicitly confirmed that permissionDecision: "allow" correctly suppressed the native prompt as of v2.1.59. Between 2.1.59 and 2.1.119 (the version I'm reproducing on), this behavior regressed for the simple case described here.

Happy to provide additional debug logs, try additional configurations, or narrow down the minimum-regression version if that helps triage.

PettHa · 3 months ago

Data point from 2.1.139 that might narrow this bug.

permissionDecision: "allow" is honored on Bash, Edit/Write/MultiEdit, MCP calls, skill invocations, Agent and WebFetch. All auto approve via the standard hookSpecificOutput shape with no dialog.

The one tool where the dialog persists is ExitPlanMode. Its tool class in the webview bundle has a custom permissionRequest that renders the dialog directly, and the upstream categorizer pushes "plan-mode" rather than "write-code". That category doesn't seem to consult hook output.

So the title may overstate the scope. If it's actually ExitPlanMode-only the report could be sharpened (and links cleanly to #46517). Happy to share repro notes.

Workaround for the regular-tools side, if useful for anyone: https://github.com/PettHa/claude-auto-plan-mode

DarkAngel-agents · 3 months ago

Confirmed fixed on 2.1.140. Write auto-approved via hook with no native prompt, matching @PettHa's findings on 2.1.139.
The regression was between 2.1.59 and 2.1.119, now resolved. I'll test ExitPlanMode separately — if that's the only remaining case, the title should be narrowed to reflect that per @PettHa's suggestion.
Thanks everyone.

claude[bot] contributor · 3 months ago

This issue was fixed as of version 2.1.140.

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.