claude-code - 💡(How to fix) Fix PreToolUse hook with permissionDecision: "deny" emits redundant <system-reminder> [1 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#56121Fetched 2026-05-05 05:57:40
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
labeled ×3

A PreToolUse hook returning permissionDecision: "deny" via the documented hookSpecificOutput format causes the runtime to send two distinct messages to the model in the same turn:

  1. The expected denial (rendered as the tool's <error> block carrying permissionDecisionReason).

  2. Separately, a <system-reminder> titled:

    PreToolUse:Bash hook blocking error from command: "/path/to/hook"

    …repeating the full deny reason text.

Only (1) is documented. (2) is redundant and creates the same "did the tool run anyway?" perception that motivated moving away from the deprecated continue: false shape — except this time the dual-message comes from a different runtime emit path that the deprecation cleanup didn't cover.

Error Message

  1. The expected denial (rendered as the tool's <error> block carrying permissionDecisionReason).

PreToolUse:Bash hook blocking error from command: "/path/to/hook" <error>permissionDecisionReason text here</error> PreToolUse:Bash hook blocking error from command: "/path/to/hook": permissionDecisionReason text here When a PreToolUse hook returns permissionDecision: "deny", the tool's <error> block is the canonical channel. No follow-up <system-reminder> should be emitted, mirroring how permissionDecision: "allow" doesn't produce one. The literal PreToolUse:Bash hook blocking error from command: likely lives in the runtime as a separate emit path from the one the deprecated continue: false block field used. A previous fix to the deprecated path (silenced its "PreToolUse:Bash hook stopped continuation" reminder) did not reach this one.

Root Cause

A PreToolUse hook returning permissionDecision: "deny" via the documented hookSpecificOutput format causes the runtime to send two distinct messages to the model in the same turn:

  1. The expected denial (rendered as the tool's <error> block carrying permissionDecisionReason).

  2. Separately, a <system-reminder> titled:

    PreToolUse:Bash hook blocking error from command: "/path/to/hook"

    …repeating the full deny reason text.

Only (1) is documented. (2) is redundant and creates the same "did the tool run anyway?" perception that motivated moving away from the deprecated continue: false shape — except this time the dual-message comes from a different runtime emit path that the deprecation cleanup didn't cover.

Fix Action

Workaround

None at the agent or hook author level. The hook output is already in the documented shape; switching back to continue: false would re-trigger the other reminder this format was designed to silence. The fix must be in the runtime.

Code Example

jq -n \
  --arg cmd "$COMMAND" \
  '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: ("denied: " + $cmd)
    }
  }'

---

<error>permissionDecisionReason text here</error>

<system-reminder>
PreToolUse:Bash hook blocking error from command: "/path/to/hook": permissionDecisionReason text here
</system-reminder>
RAW_BUFFERClick to expand / collapse

Summary

A PreToolUse hook returning permissionDecision: "deny" via the documented hookSpecificOutput format causes the runtime to send two distinct messages to the model in the same turn:

  1. The expected denial (rendered as the tool's <error> block carrying permissionDecisionReason).

  2. Separately, a <system-reminder> titled:

    PreToolUse:Bash hook blocking error from command: "/path/to/hook"

    …repeating the full deny reason text.

Only (1) is documented. (2) is redundant and creates the same "did the tool run anyway?" perception that motivated moving away from the deprecated continue: false shape — except this time the dual-message comes from a different runtime emit path that the deprecation cleanup didn't cover.

Hook output (correct shape)

The hook prints valid JSON on stdout and exits 0, no stderr:

jq -n \
  --arg cmd "$COMMAND" \
  '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: ("denied: " + $cmd)
    }
  }'

This matches the hook contract docspermissionDecision: "deny" with a permissionDecisionReason is the canonical refusal channel.

Reproduction

Configure a PreToolUse:Bash matcher pointing at a script that emits the JSON above (any deny condition will do — e.g., reject commands containing ;). Run a Bash tool call that triggers the deny. Observe in the model's view:

<error>permissionDecisionReason text here</error>

<system-reminder>
PreToolUse:Bash hook blocking error from command: "/path/to/hook": permissionDecisionReason text here
</system-reminder>

Both blocks are present. The <system-reminder> is added by the runtime independent of the hook's own output.

Expected

When a PreToolUse hook returns permissionDecision: "deny", the tool's <error> block is the canonical channel. No follow-up <system-reminder> should be emitted, mirroring how permissionDecision: "allow" doesn't produce one.

Suggested investigation

The literal PreToolUse:Bash hook blocking error from command: likely lives in the runtime as a separate emit path from the one the deprecated continue: false block field used. A previous fix to the deprecated path (silenced its "PreToolUse:Bash hook stopped continuation" reminder) did not reach this one.

May also affect other PreToolUse matchers (Write/Edit guards) using the same return shape — untested but logically symmetric.

Environment

  • Claude Code 2.1.126
  • macOS 26.4.1, Darwin 25.4.0 arm64

Workaround

None at the agent or hook author level. The hook output is already in the documented shape; switching back to continue: false would re-trigger the other reminder this format was designed to silence. The fix must be in the runtime.

extent analysis

TL;DR

The runtime should be updated to prevent emitting a redundant <system-reminder> when a PreToolUse hook returns permissionDecision: "deny".

Guidance

  • Investigate the runtime's emit path for PreToolUse hooks to identify where the redundant <system-reminder> is being generated.
  • Compare the emit path for permissionDecision: "deny" with the one used for the deprecated continue: false block field to understand why the previous fix did not silence this reminder.
  • Consider testing other PreToolUse matchers (e.g., Write/Edit guards) to determine if they are also affected by this issue.
  • Review the runtime's code to ensure that it correctly handles the hookSpecificOutput format and only emits the expected <error> block when permissionDecision: "deny" is returned.

Notes

The issue appears to be specific to the runtime and its handling of PreToolUse hooks, so a fix will likely require changes to the runtime's code.

Recommendation

Apply a runtime update to fix the issue, as the current behavior is not in line with the documented hookSpecificOutput format and is causing redundant reminders to be emitted.

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 PreToolUse hook with permissionDecision: "deny" emits redundant <system-reminder> [1 participants]