創刊準備号 · 2026年秋English · RSS
AgentKoji

Stop hookで完了を確かめる

Stop hookの続行が8回続くと、Claude Codeは打ち切ります。テストが通るまで止めないhookと、ループの防ぎ方が分かります。

要点(TL;DR)

  • Stop hook は Claude が応答を終える直前に動き、ブロックすると理由を添えて作業を続けさせられます。「終わったか」を機械の結果で確かめる場所です。
  • Stop は作業の終わりだけでなく応答のたびに動くので、例では src/ と test/ に変更がないときはテストを飛ばしています。
  • 最後の応答は transcript_path ではなく last_assistant_message で読みます。ループを防ぐため stop_hook_active が true なら止まらせます。

Stop hook は、Claude が応答を終える直前に動きます。ここでブロックすると、理由を添えて Claude に作業を続けさせられます。「本当に終わったか」を、Claude の自己申告ではなく機械の結果で確かめる場所です。この記事では、渡される JSON、ブロックの返し方、テストが落ちていたら止まらせない hook の例、ループを防ぐ仕組み、SubagentStop との違いを順に説明します。編集部の検証環境(macOS、Claude Code 2.1.306、jq 1.7.1、Node.js 26.8.2、2026年10月22日)で、ダミーの todo アプリにスクリプトを置き、ダミーの JSON を流して確かめました。実際のセッションで発火するところは見ていません(未実行)。完了報告をそのまま信じない理由はAIの「完了しました」を信じない仕組みにまとめています。

Stop がいつ動くか

Stop は、メインの Claude が応答を終えたときに動きます。ユーザーが中断したときは動かず、API のエラーで終わったときは代わりに StopFailure が動きます(出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認)。

Stop は、作業の終わりだけでなく、応答のたびに動きます(出典:https://code.claude.com/docs/en/hooks-guide 、2026-10-22確認)。テストを全部回す Stop hook は、ちょっとした質問に答えたあとにもテストを回します。下の例では、src/ と test/ に変更がなければテストを飛ばしています。

Stop には matcher がありません。書いても無視されます。

渡される JSON

共通のフィールドに加えて、次のフィールドが届きます。

フィールド中身
stop_hook_activeStop hook のせいで、すでに作業を続けている最中なら true
last_assistant_messageClaude の最後の応答の本文
background_tasksまだ動いているシェルのコマンドやサブエージェントなどの一覧
session_cronsこのセッションで予定されている起動の一覧

最後の応答を読みたいときは、transcript_path のファイルではなく last_assistant_message を使います。公式によると、Stop の時点ではトランスクリプトのファイルに最後のメッセージが入っているとは限りません。

出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認

続けさせる返し方

出力結果
JSON の {"decision": "block", "reason": "..."}Claude は止まらない。reason は必須で、続ける理由として Claude に渡る
hookSpecificOutput.additionalContextClaude は続ける。エラーではなく「Stop hook feedback」として表示される
exit 2 と stderrreason と同じ扱い。stderr が説明として Claude に渡る
exit 0 で何も出さないClaude は止まる

出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認

テストが通るまで止まらせない hook

ダミーの todo アプリ(~/projects/todo-app)で、src/ か test/ に変更があり、npm test が失敗するときだけブロックします。

#!/usr/bin/env bash
# .claude/hooks/stop-require-tests.sh  (Stop)
input=$(cat)
if [ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ]; then
  exit 0   # このフックで一度続けさせたあとなので、止まらせる
fi
cd "${CLAUDE_PROJECT_DIR:?}" || exit 0
if [ -z "$(git status --porcelain -- src test)" ]; then
  exit 0   # src/ と test/ に変更がない
fi
if npm test --silent >/dev/null 2>&1; then
  exit 0
fi
jq -n '{decision: "block",
        reason: "src/ か test/ に未コミットの変更があり、npm test が失敗しています。npm test を実行して直してから終えてください。"}'
exit 0

ダミーのリポジトリを4つの状態にして、JSON を流しました。テストは Node の組み込みのテストランナー(node --test)です。

状態stop_hook_active出力exit
src/ と test/ に変更なしfalseなし0
src/ を変えてテストが落ちるfalsedecision: "block" と理由0
同じ状態で2回目trueなし0
src/ を変えてテストが通るfalseなし0

ブロックしたときの出力です。

{
  "decision": "block",
  "reason": "src/ か test/ に未コミットの変更があり、npm test が失敗しています。npm test を実行して直してから終えてください。"
}
exit=0

テストが通る状態での1回の実行は、0.738 秒でした。テストを回す分だけ、応答のたびに時間がかかります。ブロックのあと、Claude が本当にテストを直すかどうかは確かめていません(未実行)。

ループを防ぐ

Claude Code は、Stop hook による続行が8回続くと、次のブロックを打ち切って応答を終えます。 この回数は、Claude がツールを呼ぶたびに0に戻ります。上限は環境変数 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP で上げられます。

公式のガイドは、stop_hook_active を見て、true なら早めに抜けるよう勧めています。上の例はその形で、1回止まるごとに Claude を戻すのは1回までです。テストが通るまで何度でも戻したいならこの判定を外し、上限に任せます。その場合、打ち切られるまでに最大8回ぶん、余計に使用量がかかります。

出典:https://code.claude.com/docs/en/hooks 、https://code.claude.com/docs/en/hooks-guide 、2026-10-22確認

SubagentStop との違い

SubagentStop は、サブエージェントが応答を終えたときに動きます。Stop と違って matcher があり、サブエージェントの種類(agent_type)で絞り込めます。ブロックの返し方は Stop と同じです。入力には agent_id、agent_type、サブエージェント自身のトランスクリプトのパスが加わります。プロンプトの候補を出す機能のような、Claude Code の内部のエージェントが終わったときにも動きます。

出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認

settings.json と、うまく動かないとき

Stop には matcher がないので、matcher は書きません。テストを回すので、timeout を 120 秒にしています。command hook の既定は 600 秒です。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-require-tests.sh", "timeout": 120 }
        ]
      }
    ]
  }
}

jq で読めることと、claude doctor で不正な設定が無いことを確かめました。

注意点が2つあります。1つは、スクリプトのパスを間違えたときの扱いです。Stop hook が stdout に何も出さずに exit 2 で終わり、stderr に No such file or directory のようなファイルが無いという文が出た場合、Claude Code はそれを止めないエラーとして扱います。編集部の検証環境では、無いパスを実行すると No such file or directory で終了コード 127 でした。パスを間違えても Claude がループに閉じ込められることはありませんが、ブロックもされません。

もう1つは、onFailure: "block" が Stop hook には効かないことです。Stop で exit 2 を返すと Claude を作業に戻すことになり、動かない hook を Claude が直すことはできないからです。

出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認

ほかの原因はhooksが動かないときの確認にまとめています。hook の考え方はClaude Code hooks実践にあります。

まとめ

関連記事