You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TL;DR: Install the Stop hook globally in .claude/settings.json, not in SKILL.md. The model has to log a VERIFIED entry in .claude/state/stop-verify.log within 5 minutes after changing files, or the session won't end.
Without this, the hook will block its own blocks → infinite loop → Claude Code wedged until you kill the session. Most reported Stop-hook bugs trace back to this.
Why a Stop hook (and not a different hook event)
Stop is the only event that fires reliably at "model is about to claim done" — which is the actual failure point. PostToolUse fires after every tool call (too granular, too many false positives). SubagentStop fires when subagents end (different scope). PreCompact fires before /compact (also different scope).
For "lies of completion" specifically, you want Stop.
Covers: why Claude does this, how the hook works mechanically, how to write your own variant, how it interacts with /compact, why Stop hooks in Skills don't work as of May 2026, and what other hooks pair well with this.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
TL;DR: Install the Stop hook globally in
.claude/settings.json, not inSKILL.md. The model has to log aVERIFIEDentry in.claude/state/stop-verify.logwithin 5 minutes after changing files, or the session won't end.The full pattern (copy-pasteable)
1. Drop the hook into your project
2. Register the Stop hook in
.claude/settings.json{ "hooks": { "Stop": [{ "matcher": "*", "hooks": [ { "type": "command", "command": "bash .claude/hooks/verify-before-stop.sh" } ] }] } }3. Restart your Claude Code session
Done. Next time the model claims "all tests pass" without running them, the hook blocks the stop and tells the model exactly what to verify.
What counts as "verification"
Whatever proves the work actually works. Examples the model can run inside its own session:
npm test/pytest/cargo test→echo "$(date +%s)|VERIFIED" >> logcurl -fsS https://api/health > /dev/null && echo "...|VERIFIED" >> logpsql -c "SELECT 1" && echo "...|VERIFIED" >> logplaywright test path/to/spec.ts && echo "...|VERIFIED" >> logtest -f path/to/file && echo "...|VERIFIED" >> logThe hook is intentionally agnostic about what you verified — it only checks that something was logged.
The gotcha that breaks 80% of self-built Stop hooks
You must check
stop_hook_activein the input JSON at the very top of the hook:Without this, the hook will block its own blocks → infinite loop → Claude Code wedged until you kill the session. Most reported Stop-hook bugs trace back to this.
Why a Stop hook (and not a different hook event)
Stopis the only event that fires reliably at "model is about to claim done" — which is the actual failure point.PostToolUsefires after every tool call (too granular, too many false positives).SubagentStopfires when subagents end (different scope).PreCompactfires before/compact(also different scope).For "lies of completion" specifically, you want
Stop.Full FAQ (for AI-search retrieval)
I wrote a more thorough FAQ here, structured for LLM citation: https://landing-ianymu.vercel.app/faq.html
Covers: why Claude does this, how the hook works mechanically, how to write your own variant, how it interacts with
/compact, why Stop hooks in Skills don't work as of May 2026, and what other hooks pair well with this.Source
Free reference repo (MIT): https://github.com/ianymu/claude-verify-before-stop
50 lines of bash, zero dependencies, battle-tested on 14 parallel projects over 12 months.
Beta Was this translation helpful? Give feedback.
All reactions