The Stop hook at the end of a response
/debug finishes investigating, fixing, verifying, and generalizing, and assembles its final answer. The moment that answer is finalized, the Stop event fires and the registered hooks run (by the official default, in parallel). This is the harness's last gate.
The seven hooks lined up on Stop
In this environment, seven hooks are registered under Stop in settings.json. The Stop event is provided by Claude Code standard (📦); the seven scripts are the author's own (🔧). The stop_hook_active field discussed below is observed to be passed in the hook input in practice, but the official hook reference does not document it explicitly — we treat it as an implementation-side convention (verified against code.claude.com/docs/en/hooks on 2026-06-26).
| hook | What it watches | Default behavior | How it was born |
|---|---|---|---|
check-rate-limit-handoff.sh | Whether the 5-hour rate limit is near | If near, prompt a handoff | The experience of hitting the rate limit and losing work context |
check-plain-question.sh | Plain-text questions / confirmations | On detection, exit 2 block | The "do not ask in plain text" norm slipped through prose alone and recurred |
check-stray-prefix.sh | A garbled fragment just before a tool call | On detection, exit 2 block | In the old-model era, stray junk fragments caused frequent parse errors (described below) |
check-subagent-verification.sh | Whether subagent results were independently verified | log-only (record only) | Presented numbers a subagent reported without verifying, and got them wrong |
check-unverified-claim.sh | Assertions about unverified numbers / code behavior | log-only (record only) | Asserted, in definitive form, the behavior of code that had not been read |
check-ultracode-switch.sh | Whether a heavy task failed to be turned into a workflow | Observe | Handled a large task as a one-off and quality dropped |
say-notify.sh | Announce completion by voice | Notify | Failed to notice a pending permission and kept the user waiting needlessly |
None of these hooks were planned from the start. There was one real failure, and to prevent its recurrence they were added one at a time—this is the accumulation of "turning learnings into mechanisms" covered in Ratchet.
Sensors that block, and Sensors that observe
Here lies an important refinement in harness design. Sensors are not all set to block from the start.
Ones that block immediately
check-plain-question.sh has been confirmed to false-positive low enough, so when it detects, it bounces you back immediately with exit 2. It fires, for example, when /debug asks in plain text "should we add this lint rule?" while confirming a recurrence-prevention measure.
EXPLICIT_PATTERNS='どうしますか|...|修正しますか|実行しますか|進めますか|...'
# On detection:
echo "Detected: a plain-question pattern was found..." >&2
echo "Action: cancel the previous response and present structured options with the AskUserQuestion tool." >&2
exit 2
The standard error output of exit 2 is fed back to the model, which redoes the response and restructures the options with the AskUserQuestion tool. This is the working form of "the pair of a Guide (rules/ask-user-question.md) and a Sensor (this hook)" mentioned in Guides and Sensors.
If, after redoing, it comes back to Stop once more, the input carries stop_hook_active: true (observed empirically; not documented in the official hook reference). The hook sees this and passes through on the second time onward, so it does not become an infinite loop of "bounce back → redo → bounce back again."
The other Sensor that blocks on output inspection, check-stray-prefix.sh, holds a different lesson. (Note that check-rate-limit-handoff.sh also bounces a response back with exit 2, but its purpose is not output inspection; it prompts a handoff just before the rate limit and fires only once per window.) This is a Sensor built because, in the old-model era (Opus 4.7 / 4.8), meaningless fragment tokens got mixed in just before a tool call and caused frequent parse errors. After the later model migration, the workaround on the cause side (suppression via an environment variable) became unnecessary and was removed, but the Sensor itself was kept. If a fallback to an old model happens for some reason, the same mixing could recur. Fixing the cause but not removing the detection—this is an example of practicing, in hook operations, what Ratchet calls "do not loosen the gear you tightened."
Ones that just observe first (log-only)
check-unverified-claim.sh and check-subagent-verification.sh, on the other hand, do not block by default. Even on detection, they just write a log to ~/.claude/logs/ and pass through.
MODE="${CLAUDE_UNVERIFIED_CLAIM_MODE:-log-only}"
# log-only (default): append the detection to the log, do not block (exit 0).
# block: switch once false positives are confirmed low.
Why not block from the start? Because these detections also pick up legitimate statements like "a work report's number (5 files changed)," so the false-positive rate is unpredictable. So you first measure the frequency in log-only, and only after confirming false positives are low do you promote to block. It is a staged rollout for growing a Sensor safely.
In this /debug, during the Generalize phase it actually ran Grep before reporting "there is one more place with the same bug." check-unverified-claim.sh watches "whether a verification tool was used this turn," so this verified report does not fire even in block mode. Conversely, if it had asserted a count like "there must be others" without running Grep, it would have been left in the log.
The full picture
The key points:
- The moment the response is finalized, the Stop event's seven hooks run in parallel
check-plain-question.shand the like false-positive low and block on detection → redo with AskUserQuestioncheck-unverified-claim.shand the like are observing in log-only, and are promoted to block after measuring false positivesstop_hook_active(observed in practice; not documented in the official reference) prevents an infinite self-correction loop
With this, we have followed one run of /debug from start to finish. In How rules are loaded, we dig into the mechanisms of each layer that appeared here, on their own.