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

Resolved 💬 6 comments Opened Apr 24, 2026 by DarkAngel-agents Closed May 17, 2026

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 ↗

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