メインコンテンツまでスキップ

応答終了時の Stop hook

/debug が調査・修正・検証・横展開を終え、最終的な回答をまとめます。その回答が確定する瞬間に Stop イベントが発火し、登録された hook が走ります(公式の既定では並列実行)。これがハーネスの最後の関門です。

Stop に並ぶ 7 つの hook

この環境では、settings.jsonStop に 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.sh5 時間レート上限が近いか近ければ引き継ぎを促すレート上限に直撃して作業文脈が失われた経験
check-plain-question.sh平文での質問・確認検出で exit 2 ブロック「平文で質問しない」規範が文章だけではすり抜けて再発した
check-stray-prefix.shツール呼び出し直前の文字化け断片検出で exit 2 ブロック旧モデル時代にゴミ断片の混入でパースエラーが多発した(後述)
check-subagent-verification.shsubagent 結果を独立検証したか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 ルールを追加しますか?」と平文で聞いてしまった場合に発火します。

check-plain-question.sh(抜粋)
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.shexit 2 で応答を差し戻しますが、用途は出力の検査ではなくレート上限手前での引き継ぎの促しで、同一 window で 1 回だけ発火します。これは旧モデル(Opus 4.7 / 4.8)の時代に、ツール呼び出しの直前へ意味のない断片トークンが混入してパースエラーが多発したため作られた Sensor です。その後のモデル移行で原因側の回避策(環境変数での抑制)は不要になり削除しましたが、Sensor 自体は残してあります。何かの拍子に旧モデルへフォールバックすれば同じ混入が再発しうるからです。原因を直しても検出は外さない——Ratchet でいう「締めた歯車は緩めない」を、hook の運用で実践している例です。

まず観測だけするもの(log-only)

一方 check-unverified-claim.shcheck-subagent-verification.sh は、既定ではブロックしません。検出しても ~/.claude/logs/ にログを書くだけで通過します。

check-unverified-claim.sh のモード(コメントより)
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 のロード機構 では、ここで登場した各レイヤの仕組みを単体で掘り下げます。