Skip to main content

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).

hookWhat it watchesDefault behaviorHow it was born
check-rate-limit-handoff.shWhether the 5-hour rate limit is nearIf near, prompt a handoffThe experience of hitting the rate limit and losing work context
check-plain-question.shPlain-text questions / confirmationsOn detection, exit 2 blockThe "do not ask in plain text" norm slipped through prose alone and recurred
check-stray-prefix.shA garbled fragment just before a tool callOn detection, exit 2 blockIn the old-model era, stray junk fragments caused frequent parse errors (described below)
check-subagent-verification.shWhether subagent results were independently verifiedlog-only (record only)Presented numbers a subagent reported without verifying, and got them wrong
check-unverified-claim.shAssertions about unverified numbers / code behaviorlog-only (record only)Asserted, in definitive form, the behavior of code that had not been read
check-ultracode-switch.shWhether a heavy task failed to be turned into a workflowObserveHandled a large task as a one-off and quality dropped
say-notify.shAnnounce completion by voiceNotifyFailed 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.

check-plain-question.sh (excerpt)
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.

Preventing an infinite self-correction loop

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.

The mode of check-unverified-claim.sh (from the comments)
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

Summary of this chapter

The key points:

  • The moment the response is finalized, the Stop event's seven hooks run in parallel
  • check-plain-question.sh and the like false-positive low and block on detection → redo with AskUserQuestion
  • check-unverified-claim.sh and the like are observing in log-only, and are promoted to block after measuring false positives
  • stop_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.