>_

My Claude Code Hooks Fired 574 Times, Blocked Zero [2026]

Robin||5 min
claude-codeclaude-code-hooksobservabilitynegative-resultmeasurement
Claude Code hooks fired 574 times and blocked zero - PreToolUse, PostToolUse, Stop event, settings.json hooks, hook enforcement vs observability, agent verification, jsonl ledger, self-tuning fitness loop, fugu --ultra cross-check, grok-build, Evolving Lite, primeline-ai/evolving-lite
Listen to this article (5 min)

Claude Code hooks that run on every turn feel like enforcement. Mine were not. One watched 574 turns for the exact failure it was built to stop, blocked zero of them, and I cited it to myself as proof the failure was under control.

None of the four instruments below were broken in an obvious way. Each ran, wrote a ledger, and produced a number I could point at. All four measured their own machinery rather than the thing they named. The hook system this post picks apart lives in primeline-ai/evolving-lite.

574turns observed by the gate
0blocks, ever
0 of 574turns carrying the evidence it checks for
414fitness ratings, none negative

The Claude Code hooks that fired 574 times and blocked nothing

The gate is a Stop-event hook. Its job is to catch a completion claim ("fixed", "verified", "tests pass") arriving with no evidence in the same turn. I wrote it because I kept doing exactly that. See why "done" is not done for the discipline it was meant to enforce.

Three findings from the audit, in ascending order of how much they hurt.

It is not configured to block

Blocking needs two conditions: an autonomous-session lease, and an environment variable enabling enforcement. Nothing in my live setup sets that variable, and I verified rather than assumed: zero occurrences in the settings file. Across 574 recorded events, zero blocks. An observer wearing a gate's name.

The evidence it looks for never arrives

The gate scans for three markers tagging a trigger, an effect, and a downstream consumer. Those markers appeared on 0 of 574 events. Not a low rate. Zero. It has never evaluated an actual proof, because its input does not exist.

Stop-hook ledger audit, 574 events and zero blocks - Claude Code hooks, PreToolUse, PostToolUse, Stop event, settings.json hook registration, observe mode vs blocking, jsonl ledger, agent observability, self-tuning fitness loop, verification gate, Evolving Lite, git clone primeline-ai/evolving-lite
The audit that started this. 574 events, zero blocks, and the evidence markers the gate checks for never arrived once. (click to expand)

Turning enforcement on may not help

It would have flagged 0 of 35 hand-reviewed turns, which bounds the true rate under roughly 8 percent, not at "never." Those turns were written by the person who knows what the gate looks for, so the sample is biased toward zero by construction. I sent the decision to fugu --ultra and grok-build, and both called it under-determined.

Can Claude Code hooks check whether a claim is true?

No. Claude Code hooks can only check the shape of a claim, and that distinction is the one that generalizes past my setup.

When the gate does evaluate evidence it verifies two things: that a cited file path exists on disk, and that the text carries a concrete token such as a path, an exit code, a test result or a count.

Fabricated but well-formed evidence passes. A confident sentence with a real path and a plausible exit code satisfies every condition the hook imposes.

The distinction that matters

Everything I built measured whether a claim carried a token. Nothing measured whether the claim was true. In ten weeks of instrumentation I never once measured the second property. The rate at which my confident claims are actually false is still unknown.

The detector that rotted without telling me

Silence that looked like success

A separate detector watched for a wording pattern. It logged 152 matches, then went silent for 22 days while four real instances went past it. Its phrase list was written in May, and by July my vocabulary had moved on. Nothing alerted me, because a detector that stops matching produces the same output as a problem that stopped happening.

Precision failed the other way too. One match fired on a phrase that is simply "try again," which is not the behavior at all. Related: self-correcting workflows with PreToolUse hooks.

The opposite failure, same family

A monitor elsewhere fired 695 times, produced 3 findings, and was ignored every time. Both of these are Claude Code hooks that ran perfectly and taught me nothing, and both patterns are visible in primeline-ai/evolving-lite.

Too loose: 695 fires
  • -Fires on a large share of turns
  • -3 real findings across its lifetime
  • -You build a filter reflex within a day
  • -Failure mode: you stop reading it
Too tight: 22 days silent
  • +152 matches, then nothing
  • +4 real instances passed through unflagged
  • +Phrase list aged out, no alert
  • +Failure mode: you read silence as health

This lives in primeline-ai/evolving-lite - the self-evolving Claude Code plugin. Free, MIT, no build step.

The score that could never say no

The worst one was not a hook. It was a scoring loop, and it is the failure I would most want other people to check for.

A fitness score rated delegation decisions and fed the rating into the routing threshold. The negative branch read its task type from a narrower source than the positive branch, so a bad rating was structurally unreachable.

414ratings recorded
0negative, ever
weeksit ran before anyone checked

The sibling defect was worse, because the field name lied. A modifier documented as reading a "success rate" actually read a field recording whether a task was delegated, not whether delegating worked. Delegating dropped the threshold, which made delegating more likely, which fed the counter that dropped the threshold. A positive feedback loop wearing a fitness signal's name. I deleted it rather than repairing it.

If you have any self-tuning loop driven by Claude Code hooks, the check is one line: prove the field you read records an outcome and not an action. Mine did not, and the variable name gave no hint.

Note that this one was not a hook defect. The hooks wrote their rows correctly. The consumer of those rows was wrong, which is a failure mode no amount of hook testing surfaces.

Why does zero not mean safe?

Because in three separate observe windows, zero meant the measurement had never worked. I nearly closed all three as proven safe.

One recorded 0 of 260 rows in the mode it watched, because a different safety gate blocks that mode from running at all, so no such event exists. Another had zero rows because its producer had never been invoked once. Not slow, not rare. Never run.

A third variant is easy to miss: a keyword matcher scores a perfect 1.0 on its 53 curated test cases while matching 93 percent of real prompts. The test set was not representative, so the score measured the test set rather than the matcher.

The pattern behind all four failures

The instrument carried the exact defect it was built to detect.

- The one line worth taking away

The gate against unbacked claims made an unbacked claim about itself. The scheduler built to make things fire did not fire. My tests never caught it, because they shared the code's mental model.

This is also why deleting instructions beats adding another one. Anthropic's context engineering guidance reports cutting roughly 80% of Claude Code's own system prompt with no eval loss, and it was safe because the evaluations existed first. Mine did not.

Four checks before you trust your own instrument

Run these in order on any hook you rely on
Does the producer exist, and has it ever run once?
v
Can it report a negative? Trigger that path deliberately
v
Does each field name match what the field records?
v
Is the checker part of the thing being checked?
  1. Does the producer exist and has it ever run? An absent output file does not distinguish broken from never-invoked. Run it by hand before filing a bug.
  2. Can this thing report a negative? Trigger the code path that produces a block or a bad rating. If you cannot, the positive results mean nothing.
  3. Does every field name match what the field records? Read the write site, not the variable name. "Success" was a dispatch counter in mine.
  4. Is the checker part of the thing being checked? If yes, ask a model outside your stack where your code commits the failure it hunts. I use fugu --ultra and grok-build for this. It is the only one of the four I could not do alone, and it found two of these.

The gate in this post is public: hooks/scripts/forced-verify-stop-gate.py in primeline-ai/evolving-lite. Its own docstring says it never blocks and that autonomy is off by default, so you can check the claim rather than take my word for it. New to the event model? Anthropic's hooks guide is the reference. Related: my hook automation setup and auditing my own correction-rate measurement, the same bug class one layer up.

Honest scope

n=1 developer, my own instruments, and the counts come from my own ledgers, which is exactly the dependency this post warns about. The 574-event figure is the audited window, not the ledger's lifetime. I have not measured what fraction of my completion claims were actually false, only that nothing I built was measuring it. That gap is open and I am not reporting it as closed.

FAQ

Why did my Claude Code hook never block anything?+
Mine required an environment variable that nothing in my live setup sets, plus an autonomous-session lease. It ran on all 574 events and recorded them faithfully. It was an observer the whole time, and its filename said gate.
Can Claude Code hooks verify that a claim is true?+
Not with pattern matching. Mine verified that a cited path exists and the text carries a concrete token like an exit code. Well-formed fabricated evidence passes all of that. Shape and truth are different properties.
How do I know if my detector has rotted?+
Track its match rate over time, not just its matches. Mine logged 152 hits then went silent 22 days through four real instances because its phrase list aged out. Detector silence and absence of the problem look identical.
What does a zero in an observe window actually mean?+
Usually that the measurement never worked. Three of mine read zero because the producer had never run or the event class was blocked upstream. Distinguish never-invoked from genuinely-rare before reading zero as safe.
How do I audit a self-tuning loop in Claude Code?+
Read the write site of the field it treats as feedback and confirm it records an outcome rather than an action. Mine read a dispatch flag named as a success rate, which made the loop positive feedback rather than fitness.
Should I delete Claude Code hooks that never fire usefully?+
Delete or rename them. A hook that cannot block should not be called a gate, because you will eventually cite it as evidence. I kept mine as an observability instrument with the enforcement claim removed from its documentation.

>_ Get the free Claude Code guide

>_ No spam. Unsubscribe anytime.

>_ Related