応答終了時の Stop hook
/debug が調査・修正・検証・横展開を終え、最終的な回答をまとめます。その回答が確定する瞬間に Stop イベントが発火し、登録された hook が走ります(公式の既定では並列実行)。これがハーネスの最後の関門です。
Stop に並ぶ 7 つの hook
この環境では、settings.json の Stop に 7 つの hook が登録されています。Stop イベントは Claude Code 標準の提供(📦)で、7 本のスクリプトは筆者の自作(🔧)です。後述する stop_hook_active は実装上 hook 入力に渡される動作を確認していますが、公式 hook reference には明示記載がなく実装側の慣習として扱っています(2026-06-26 code.claude.com/docs/en/hooks 確認)。
| hook | 何を見るか | 既定の動作 | 生まれた経緯 |
|---|---|---|---|
check-rate-limit-handoff.sh | 5 時間レート上限が近いか | 近ければ引き継ぎを促す | レート上限に直撃して作業文脈が失われた経験 |
check-plain-question.sh | 平文での質問・確認 | 検出で exit 2 ブロック | 「平文で質問しない」規範が文章だけではすり抜けて再発した |
check-stray-prefix.sh | ツール呼び出し直前の文字化け断片 | 検出で exit 2 ブロック | 旧モデル時代にゴミ断片の混入でパースエラーが多発した(後述) |
check-subagent-verification.sh | subagent 結果を独立検証したか | log-only(記録のみ) | subagent の報告した数値を検証せず提示して外した |
check-unverified-claim.sh | 未検証の数値・コード挙動の断定 | log-only(記録のみ) | 読んでいないコードの挙動を断定形で語ってしまった |
check-ultracode-switch.sh | 重いタスクを workflow 化し損ねていないか | 観測 | 大規模タスクを単発処理して品質が落ちた |
say-notify.sh | 完了を音声で知らせる | 通知 | 許可待ちに気づかず無駄に待たせた |
どの hook も、最初から計画して作られたものではありません。実際の失敗が 1 つあり、その再発を防ぐために 1 本ずつ増えてきた——これが Ratchet で扱う「学びを仕組みに変える」の蓄積です。
ブロックする Sensor と、観測する Sensor
ここに、ハーネス設計の重要な工夫があります。Sensor は最初から全部ブロックさせるわけではありません。
すぐブロックするもの
check-plain-question.sh は、誤検出が十分に低いと確認できているので、検出したら即 exit 2 で差し戻します。たとえば /debug が再発防止策の確認で「この lint ルールを追加しますか?」と平文で聞いてしまった場合に発火します。
EXPLICIT_PATTERNS='どうしますか|...|修正しますか|実行しますか|進めますか|...'
# 検出したら
echo "Detected: 平文質問パターンが検出されました..." >&2
echo "対応: 直前の応答を取り消し、AskUserQuestion tool で構造化された選択肢を提示してください。" >&2
exit 2
exit 2 の標準エラー出力はモデルにフィードバックされ、応答をやり直して AskUserQuestion ツールで選択肢を構造化し直します。これが Guides と Sensors で触れた「Guide(rules/ask-user-question.md)と Sensor(この hook)のペア」の実働です。
やり直し後にもう一度 Stop へ来ると、入力に stop_hook_active: true が渡されます(実装上の挙動として観測済み、公式 hook reference には明示記載なし)。hook はこれを見て 2 回目以降は通過するので、「差し戻し → やり直し → また差し戻し」の無限ループになりません。
出力検査でブロックするもう 1 本の Sensor、check-stray-prefix.sh には、別の教訓が詰まっています。なお check-rate-limit-handoff.sh も exit 2 で応答を差し戻しますが、用途は出力の検査ではなくレート上限手前での引き継ぎの促しで、同一 window で 1 回だけ発火します。これは旧モデル(Opus 4.7 / 4.8)の時代に、ツール呼び出しの直前へ意味のない断片トークンが混入してパースエラーが多発したため作られた Sensor です。その後のモデル移行で原因側の回避策(環境変数での抑制)は不要になり削除しましたが、Sensor 自体は残してあります。何かの拍子に旧モデルへフォールバックすれば同じ混入が再発しうるからです。原因を直しても検出は外さない——Ratchet でいう「締めた歯車は緩めない」を、hook の運用で実践している例です。
まず観測だけするもの(log-only)
一方 check-unverified-claim.sh と check-subagent-verification.sh は、既定ではブロックしません。検出しても ~/.claude/logs/ にログを書くだけで通過します。
MODE="${CLAUDE_UNVERIFIED_CLAIM_MODE:-log-only}"
# log-only(デフォルト): 検出をログに追記、exit 0 で block しない。
# block: 誤検出が低いと確認できたら切替。
なぜ最初からブロックしないのか。これらの検出は「作業報告の数値(5 ファイル変更)」のような正当な発言も拾ってしまい、誤検出が読めないからです。そこでまず log-only で頻度を測り、誤検出が低いと確認できてからブロックに昇格させます。Sensor を安全に育てるための段階導入です。
今回の /debug は、Generalize フェーズで実際に Grep を走らせてから「同じバグが他に 1 箇所ある」と報告しています。check-unverified-claim.sh は「このターンで検証ツールを使ったか」を見るので、検証済みのこの報告は、たとえ block モードでも発火しません。逆に、Grep せずに「他にもあるはず」と件数を断定していたら、ログに残ります。
全体像
要点は次のとおりです。
- 応答確定の瞬間に Stop イベントの 7 つの hook が並列に走る
check-plain-question.shなどは誤検出が低く、検出で即ブロック → AskUserQuestion でやり直しcheck-unverified-claim.shなどは log-only で観測中で、誤検出を測ってからブロックに昇格させるstop_hook_active(実装挙動として観測、公式 reference 未掲載)で自己修正の無限ループを防ぐ
これで /debug 1 回の流れを最初から最後まで追えました。rule のロード機構 では、ここで登場した各レイヤの仕組みを単体で掘り下げます。