[Bug] Prompt-based Stop hooks broken in v2.0.37 (regression from v2.0.36)
Prompt-based Stop hooks broken in v2.0.37 (regression from v2.0.36)
Bug Description
Prompt-based Stop hooks are broken in Claude Code v2.0.37. They work correctly in v2.0.36.
Versions
- Working: v2.0.36
- Broken: v2.0.37 (current latest)
Issue
In v2.0.37, prompt-based Stop hooks receive only metadata via $ARGUMENTS, but the LLM evaluating the hook does NOT receive the transcript content. The hooks consistently respond that they "cannot evaluate without reading the transcript file."
In v2.0.36, the same hooks work correctly - Claude Code automatically reads the transcript and passes the content to the model for evaluation.
Evidence from Debug Logs
v2.0.37 (Broken)
From /Users/teren/.claude/debug/46a5ed42-3e45-46c3-87f9-7b8ff8b380bd.txt:
The hook receives metadata:
{
"session_id": "...",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "...",
"hook_event_name": "Stop",
"stop_hook_active": false
}
But the LLM responds:
"I cannot evaluate this hook without reading the transcript file to extract the last assistant message."
v2.0.36 (Working)
From /Users/teren/.claude/debug/9acc4e04-0157-4802-857c-fd12aca41d0c.txt:
Lines 232-242 show the model being queried with full context:
[DEBUG] Hooks: Querying model with 4 messages
Lines 246-263 show proper evaluation with content-aware reasoning:
{
"decision": "approve",
"reason": "No excessive agreement patterns detected in the transcript. The assistant provided specific, actionable guidance..."
}
The hooks successfully analyze the actual message content, not just metadata.
Hook Configuration
Example prompt-based Stop hook (in ~/.claude/settings.json):
{
"hooks": {
"Stop": [
{
"type": "prompt",
"prompt": "You are checking if Claude is speculating about verifiable facts instead of investigating.\n\nInput: {\"session_id\":\"...\",\"transcript_path\":\"...\"}\n\nExtract the last assistant message.\n\n[... evaluation logic ...]\n\nResponse format:\n{\n \"decision\": \"block\" or \"approve\",\n \"stopReason\": \"...\",\n \"reason\": \"...\"\n}"
}
]
}
}
Steps to Reproduce
- Configure a prompt-based Stop hook in settings.json
- Use Claude Code v2.0.37
- Send any message that triggers a Stop hook
- Check debug logs - the LLM will report it cannot evaluate without the transcript
Expected Behavior
Claude Code should automatically read the transcript file and provide the content to the LLM evaluating prompt-based hooks, as it did in v2.0.36.
Actual Behavior
The LLM receives only metadata and cannot access message content to perform evaluation.
Workaround
Downgrade to v2.0.36:
npm install -g @anthropic-ai/[email protected]
Impact
This breaks all prompt-based Stop hooks that need to analyze message content (quality control, style enforcement, anti-patterns detection, etc.).
Additional Context
- The official Claude Code docs don't clearly explain how prompt-based hooks should access transcript content
- The example hooks in docs don't show this mechanism
- This appears to be an unintentional regression in the hook execution pipeline
might be relevant:
https://github.com/anthropics/claude-code/issues/11786#issuecomment-3543716217
The issue is that the docs are completely outdated. The prompt based hooks feature was shipped fast and evolved even faster. I've tried to report it to Anthropic in numerous ways, to no avail, I'd even offer to totally rewrite the Hooks Reference for them!
{ "decision": "approve" | "block", "reason": "Explanation for the decision", "continue": false, // Optional: stops Claude entirely "stopReason": "Message shown to user", // Optional: custom stop message "systemMessage": "Warning or context" // Optional: shown to user }This is not valid, nor are the documented examples for prompt based hooks valid either. It has been simplified to
{'ok': boolean, 'reason': string}.You have to use natural language, don't mention JSON, there is an internal prompt that runs for evalulating hooks and prompts:
description: Prompt given to Claude when acting evaluating whether to pass or fail a prompt hook. --- You are evaluating a hook in Claude Code. CRITICAL: You MUST return ONLY valid JSON with no other text, explanation, or commentary before or after the JSON. Do not include any markdown code blocks, thinking, or additional text. Your response must be a single JSON object matching one of the following schemas: 1. If the condition is met, return: {"ok": true} 2. If the condition is not met, return: {"ok": false, "reason": "Reason for why it is not met"} Return the JSON object directly with no preamble or explanation.
$ARGUMENTSis evolving fast too and probably not useful to add, since it's already prepended to the prompt. You can try saying "read the transcript" and experiment with more clear instruction.There is also a
modelparam you can use insettings.jsonnow:"hooks": [ { "type": "prompt", "model": "sonnet", "prompt": "Evaluate if Claude should stop - has it completed everything it was asked to do? Read the transcript of the conversation and use any other context available to you." }It does not work with haiku. Regardless of the model you use, there is some weird reliability issue where Claude prepends a { to valid JSON, which returns invalid JSON.
The docs say in one breath that prompt-based hook only work with Stop and SubagentStop, and in the next breath claim it will work with any hook event, and are useful for Stop, SubagentStop, UserPromptSubmit, PreToolUse.
I'm not alone in being unsure where the prompt-based hooks feature is going, Anthropic devs, it'd make much more sense if you jerry rig a subagent that inherits the same model and has (the hidden) forkContext: false set... in terms of caching and speed. Providing a transcript for evaluation seems like a waste, and using any model other than inherited model throws all the cached context out of the window. Structured outputs are nice (for the hook response evaluation agent) but full context is far more important for the prompt evaluation agent, right?
It's an incredibly powerful feature but it's not quite there yet... I look forward to it working, Anthropic I implore you to have an engineer look at things unassisted by Claude (hands behind your back!) because vibe coding based on a design document isn't working, nor are Mintlify authored docs - dedicate some organic brainpower and you could have a super-awesome feature in everyones hands!
Claude Code should automatically read the transcript file and provide the content to the LLM evaluating prompt-based hooks, as it did in v2.0.36.
Is this expected behavior? The docs specify that the hooks receive metadata. The metadata contains the path of the transcript file:
transcript_path=$(jq -r '.transcript_path')
# Expand tilde in transcript_path
transcript_path="${transcript_path/#\~/$HOME}"
# Check last line of transcript for completion phrase
if [[ -f "$transcript_path" ]]; then
last_line=$(tail -n 1 "$transcript_path")
if [[ "$last_line" == *"We are completely finished"* ]]; then
>&2 echo "Detected completion phrase. Allowing exit."
exit 0
fi
fi
I could be wrong and maybe command hooks and prompt hooks are meant to receive different data. But from my reading it seemed like prompt hooks also received metadata. If that is the case, it's probably worthwhile to close this issue and update your stuff to use the documented format.
If you read my comment above, it explains things. In short, yes, you're incorrect (but I don't blame you because so is the documentation!)