claude-code - 💡(How to fix) Fix [BUG] PreToolUse hook permission decisions silently discarded when returned in flat format (no hookSpecificOutput wrapper) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#48760Fetched 2026-04-16 06:51:44
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×5commented ×1

Error Message

Error Messages/Logs

(No error messages — that is the bug. The hook exits 0 with no output, no warnings, no telemetry events, and no indication that the permission decision was discarded. The only observable signal is that permission prompts continue to fire for tool calls the gatekeeper allowed.)

Code Example

(No error messages — that is the bug. The hook exits 0 with no output, no warnings, no telemetry events, and no indication that the permission decision was discarded. The only observable signal is that permission prompts continue to fire for tool calls the gatekeeper allowed.)
RAW_BUFFERClick to expand / collapse

Preflight Checklist

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

What's Wrong?

Claude Code's hook output parser at cli.js line 8045 only reads hookSpecificOutput.permissionDecision for PreToolUse permission decisions. Hook scripts that return the flat top-level format {"permissionDecision": "allow"} have their decisions silently ignored — the hook runs, exits 0, logs its decision, and produces no visible failure, but the permission subsystem never receives the verdict.

The failure is invisible at every observable layer. A user who built a PreToolUse gatekeeper hook with the flat format will see the hook running on every tool call, exit code 0, log entries accumulating as expected — and no warning, no deprecation notice, no telemetry event. And yet every permission decision the hook makes is discarded. The user sees every permission prompt the gatekeeper was designed to suppress and reasonably attributes the approval tax to normal Claude Code behavior. In my case, the bug went undetected for months until a deep-read of cli.js revealed the format mismatch.

Any user who built a PreToolUse permission hook with the flat format is silently running without gatekeeper protection. The fix on the hook side is trivial (~6 lines to wrap the output in hookSpecificOutput with hookEventName: "PreToolUse"), but users cannot apply the fix if they don't know the bug exists.

What Should Happen?

Either (a) the parser accepts both formats (flat and wrapped) for backward compatibility, or (b) Claude Code emits a visible warning (stderr, telemetry, or a one-time notice in the session) when a hook returns a recognized permission-decision field at the wrong nesting level, so users can discover the format issue before spending months with a silently-disabled gatekeeper.

If the hookSpecificOutput wrapper is mandatory, the hook development docs should state this explicitly with a prominent example of the correct shape. The flat format is a natural shape for a hook author to reach for.

Error Messages/Logs

(No error messages — that is the bug. The hook exits 0 with no output, no warnings, no telemetry events, and no indication that the permission decision was discarded. The only observable signal is that permission prompts continue to fire for tool calls the gatekeeper allowed.)

Steps to Reproduce

Create a PreToolUse hook script (shell or Python) that outputs the flat JSON format to stdout:

{"permissionDecision": "allow", "permissionDecisionReason": "testing"} Configure the hook in settings.json under hooks.PreToolUse:

{ "hooks": { "PreToolUse": [ { "hooks": [ { "type": "command", "command": "bash /path/to/hook.sh" } ] } ] } } Trigger a tool call that would normally prompt for approval (for example, any Bash command not in the allow list).

Observe: the approval prompt fires. The hook has already decided "allow" and logged its decision, but the permission subsystem did not receive the verdict.

Rewrite the hook output to use the wrapped format:

{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "testing"}} Re-trigger the same tool call. The approval prompt no longer fires. The hook decision now reaches the permission subsystem.

The difference between steps 4 and 6 is the only change that makes the gatekeeper functional.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.109

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Architecture: arm64 (Apple Silicon) Node.js: v25.6.1 Install path: /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js Shell: zsh

Context for the investigation:

This bug was discovered during a structured reliability investigation of my Claude Code workflow. I spent approximately 10-15 hours across multiple sessions diagnosing what I thought was baseline approval friction, before a dedicated deep-read of cli.js revealed that my PreToolUse gatekeeper hook had been emitting the wrong output format since it was built and that every classification decision it made was being silently discarded by the parser at line 8045.

The primary code reference is cli.js line 8045, where the hook output parser reads hookSpecificOutput.permissionDecision and does not fall back to the top-level permissionDecision field. A parallel research finding is that PreToolUse hook decisions short-circuit the normal permission check (including the expansion obfuscation classifier at cli.js L1870 function C68), which means a working gatekeeper would have eliminated most of the approval prompts I experienced. The silently-disabled gatekeeper meant I was paying friction the gatekeeper was specifically designed to eliminate.

I am also filing a separate bug report on hardcoded Bash safety checks that fire on legitimate research workflows. The two bugs interact: with a silently-disabled gatekeeper, there is no intermediate enforcement layer that could absorb the hardcoded check failures, so the user experiences the maximum possible friction.

extent analysis

TL;DR

The issue can be fixed by modifying the hook output to use the wrapped format, specifically including the hookSpecificOutput object with hookEventName set to "PreToolUse", to ensure the permission decision is correctly parsed by Claude Code.

Guidance

  • Modify the hook script to output the wrapped JSON format: {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "testing"}}
  • Verify the fix by re-triggering the tool call and checking that the approval prompt no longer fires
  • Update the hook development docs to explicitly state the required output format with a prominent example
  • Consider adding a visible warning or telemetry event when a hook returns a recognized permission-decision field at the wrong nesting level to help users discover format issues

Example

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "testing"
  }
}

Notes

The fix requires modifying the hook output format, which may require updates to existing hook scripts. The issue may not be specific to the reported version of Claude Code, and further investigation may be needed to determine if it's a regression.

Recommendation

Apply the workaround by modifying the hook output to use the wrapped format, as this is a straightforward fix that can be implemented immediately.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] PreToolUse hook permission decisions silently discarded when returned in flat format (no hookSpecificOutput wrapper) [1 comments, 2 participants]