claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[BUG] Hookify plugin not passing blocking messages to Claude (missing permissionDecisionReason)

Open basher83 opened this issue 2 months ago • 5 comments

Bug Description

The hookify plugin blocks tool calls correctly but the detailed blocking message is only shown to the user, not passed to Claude. This prevents Claude from understanding why a tool was blocked and how to fix it.

Root Cause

In plugins/hookify/core/rule_engine.py (lines 72-79), when blocking a PreToolUse, hookify returns:

return {
    "hookSpecificOutput": {
        "hookEventName": hook_event,
        "permissionDecision": "deny"
    },
    "systemMessage": combined_message
}

Per the hooks documentation, systemMessage is shown to the user, but permissionDecisionReason is what gets passed to Claude when permissionDecision is "deny".

Expected Behavior

Claude should receive the detailed blocking message (e.g., "Use uv run pytest instead of pytest") so it can correct its behavior.

Actual Behavior

Claude only receives a generic "Hook PreToolUse:Bash denied this tool" message without the helpful guidance from the hook's markdown body.

Proposed Fix

Add permissionDecisionReason to the hookSpecificOutput:

return {
    "hookSpecificOutput": {
        "hookEventName": hook_event,
        "permissionDecision": "deny",
        "permissionDecisionReason": combined_message  # Add this line
    },
    "systemMessage": combined_message
}

Environment Info

  • Discovered after PermissionRequest hooks update (changelog: "Enable PermissionRequest hooks to process 'always allow' suggestions")
  • Hookify was merged in PR #11752 before permissionDecisionReason was available

Reproduction

  1. Create a hookify rule that blocks a command (e.g., hookify.require-uv.local.md)
  2. Have Claude try to run the blocked command
  3. Observe that Claude only sees "denied" without the detailed guidance

basher83 avatar Nov 26 '25 08:11 basher83

Fix confirmed working locally.

Added permissionDecisionReason to hookSpecificOutput in rule_engine.py:

return {
    "hookSpecificOutput": {
        "hookEventName": hook_event,
        "permissionDecision": "deny",
        "permissionDecisionReason": combined_message  # Added this line
    },
    "systemMessage": combined_message
}

Claude now receives the full detailed blocking message (tables, explanations, guidance) instead of just "Hook PreToolUse:Bash denied this tool".

basher83 avatar Nov 26 '25 08:11 basher83

Related: UserPromptSubmit hooks have the same problem

Just discovered that UserPromptSubmit hooks also don't pass messages to Claude when using systemMessage.

Root Cause

For UserPromptSubmit hooks, the message must be in hookSpecificOutput.additionalContext, not systemMessage.

Fix

# Before (broken)
return {
    "systemMessage": message
}

# After (working)
return {
    "hookSpecificOutput": {
        "hookEventName": "UserPromptSubmit",
        "additionalContext": message
    }
}

Summary of hook output fields needed for Claude to receive messages:

Hook Event Field Required
PreToolUse hookSpecificOutput.permissionDecisionReason
PostToolUse hookSpecificOutput.permissionDecisionReason
UserPromptSubmit hookSpecificOutput.additionalContext
Stop reason (for blocking)

This should probably be documented more clearly in the hooks documentation - systemMessage appears to only be shown to the user, while hook-specific fields are needed to pass context to Claude.

basher83 avatar Dec 07 '25 09:12 basher83

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

github-actions[bot] avatar Jan 06 '26 10:01 github-actions[bot]

So both warn and block reason does not get back to Claude. This defeats the purpose of these pretty much.

anthony-spruyt avatar Jan 08 '26 13:01 anthony-spruyt