CLAUDE_CODE_STOP_HOOK_BLOCK_CAPとは — Stop hookの無限ブロックを止める環境変数
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPは、Stop/SubagentStop hookが会話終了を連続でブロックできる回数の上限を決める環境変数です。既定は8回、0で無効化できます。
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP は、StopフックとSubagentStopフックが会話の終了を連続でブロックできる回数に上限をかける環境変数です。既定値は8回で、達するとClaude Codeがフックの判断を上書きしてターンを終わらせます。0 を指定すれば上限そのものを無効化できます。命令文のprompt型Stop hookの判定はv2.1.294で改善されています。prompt型の返す値はprompt hookでStopを判断させる記事にまとめています。
フックの終了条件を書き間違えたときの、最後の安全網にあたります。設定は単純ですが、何を数え、どこまでが上限の対象かを知らないと使いどころを誤ります。
8回で打ち切られると何が起きるか
Claudeが応答を終えるたびにStopフックが発火します。フックが decision: "block" を返すと、Claudeは終わらずに作業を続けます。続けた後にもう一度Stopが発火し、フックがまた block を返せば、理屈のうえでは無限に繰り返せます。
テストが通るまで終わらせない、レポートを書き終えるまで終わらせない、といった用途ではこれが仕様どおりの動きです。問題になるのは、判定条件が二度と満たされないフックです。
上限に達するまでの流れ
- 1
Claudeが応答を終え、Stopフックが発火する
ユーザーが中断した場合は発火しません。APIエラーで終わった場合はStopではなくStopFailureが発火します。
- 2
フックが継続を要求する
decision: "block"かadditionalContextのどちらでも、継続としては同じに数えられます。 - 3
Claudeが作業を続け、入力の stop_hook_active が true になる
次のStop発火では、フックは自分の要求で継続中だと分かります。
- 4
継続が8回連続したあと、次のblockが上書きされる
Claude Codeがフックの判断を無視してターンを終わらせます。画面には、Stopフックの連続ブロックが多すぎたという警告が出ます。
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP の値を変えられるのは、この8回の部分だけです。
「8回連続」の言い回しは公式ページごとに違う
環境変数の一覧は「ブロックを連続で返せる最大回数」と書き、hooksリファレンスは「8回連続で継続させた後、次のブロックを上書きする」と書いています。トラブルシューティングの節だけは「進捗なしに8回ブロックした」と表現します。
環境変数一覧とhooksリファレンスは「連続」と書くだけで、hooksガイドの「進捗なしに」にあたる「進捗」の定義も、3つのページには載っていません。連続回数で打ち切られるものとして設計し、途中で別の理由が挟まったら数え直しになるかどうかに依存しないフックにしておくのが安全です。
上限に頼らないフックの書き方
公式のトラブルシューティングは、まずフック自身が継続中かどうかを見るよう勧めています。入力の stop_hook_active が true なら、すでに自分の要求で継続している最中です。
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Claudeの終了を許可する
fi
# ... 以降にフック本来の判定を書くこの書き方には代償があります。継続が1回で済むフックになるため、「テストが全部通るまで何度でも直させる」用途には使えません。反復が必要なフックでは、stop_hook_active ではなく、判定対象そのもの(テスト結果など)で終了を決めます。
もう1つの判断材料が、Stop入力に入る background_tasks と session_crons です。background_tasks は実行中のシェルコマンドやサブエージェント、モニター、ワークフロー、MCPタスクなどの配列、session_crons は CronCreate や /loop で登録された実行予定の配列です。
バックグラウンドの作業を待っているだけのセッションに対して、フックが「応答が終わった」という理由でブロックし続けると、残り回数が無駄に減ります。2つの配列が空なら、本当にやることがない状態です。
配列の各要素には種類(type)が付き、shell、subagent、monitor、workflow、teammate、cloud session、MCP task といったラベルで、どの機能が作ったタスクかを区別できます。実行中のタスクがあるあいだはブロックしない、という判定は次のように書けます。
#!/bin/bash
INPUT=$(cat)
RUNNING=$(echo "$INPUT" | jq '.background_tasks | length')
if [ "$RUNNING" -gt 0 ]; then
exit 0 # バックグラウンド作業の完了を待っている最中なので、ブロックしない
fiもう1点、Claudeの最終応答のテキストは last_assistant_message に入っています。通知や読み上げのように直前のターンに反応するフックでは、transcript_path のファイルを読むより、このフィールドを使うほうが確実です。公式は、Stop時点でトランスクリプトに最後のメッセージが入っている保証はない、と説明しています。
blockとadditionalContextの違い
継続の要求は decision: "block" だけではありません。hookSpecificOutput.additionalContext で指示を返す方法もあります。
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}継続を要求する2つの返し方
decision: block
reason が必須です。トランスクリプトにはフックエラーとして表示されます。終了コード2で終わり、標準エラーにメッセージを書く方法でも同じ経路になり、そのテキストが reason の代わりにClaudeへ渡ります。
additionalContext
フックが設計どおりに動いているときの助言向けです。トランスクリプトには「Stop hook feedback」と表示され、フックエラーの通知は出ません。
見た目は違っても、stop_hook_active の付与と連続回数のカウントは共通です。additionalContext を使えば数えられない、という抜け道はありません。
設定のしかた
シェルの環境変数として渡すか、settings.json の env キーに書きます。
export CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=15
claudeチームで同じ上限を共有するなら、プロジェクトの設定ファイルに書いたほうが再現性があります。
{
"env": {
"CLAUDE_CODE_STOP_HOOK_BLOCK_CAP": "15"
}
}具体例 — テストが通るまで継続させるStopフック
Stopイベントはマッチャーを持たないため、matcher を書かずに登録します。応答が終わるたびに発火し、判定はスクリプトに任せます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/require-green-tests.sh"
}
]
}
]
}
}${CLAUDE_PROJECT_DIR} はプロジェクトルートを指す変数で、リポジトリの場所によらずスクリプトのパスを固定できます。スクリプトの骨格は次のとおりです。
#!/bin/bash
# 失敗の出力を reason に載せて、Claudeに直させる
if OUT=$(npm test 2>&1); then
exit 0 # 成功なら何も出さずに終了を許可する
fi
jq -n --arg r "テストが失敗しています。直してください: ${OUT: -2000}" \
'{decision: "block", reason: $r}'テストの母数が多いリポジトリでは、1回の継続で直せる失敗が1〜2件ということも珍しくありません。8回のブロックで打ち切られると、直し切る前にターンが終わります。こうした構成が、上限を引き上げる判断が必要になる典型です。
SubagentStopにも同じ上限がかかる
サブエージェントの完了時に発火するSubagentStopフックにも、同じ数値が適用されます。入力には stop_hook_active に加えて agent_id、agent_type、agent_transcript_path が入ります。
agent_transcript_path は、メインセッションの transcript_path とは別の、subagents/ 配下にあるサブエージェント専用の記録です。
自分が起動していないサブエージェントでも発火する
SubagentStopは、Claudeが起動したサブエージェントだけを対象にしません。プロンプト提案や /btw のような、Claude Code自身の機能が内部で走らせるエージェントが終わったときにも発火します。
その場合の agent_type は、セッションが --agent や agent 設定で動いているときはその名前、そうでなければ空文字列です。エージェント名を指定したマッチャーは、空の agent_type には一致しません。マッチャーを省略した、あるいは "" や "*" のフックは、空の場合も実行されます。
SubagentStopフックが受け取る background_tasks と session_crons は、サブエージェント自身のものではなく、親セッションの範囲です。サブエージェントが終わったかどうかと、セッション全体に作業が残っているかどうかは別の話になります。
ブロックを返すSubagentStopフックを書くなら、agent_type を見て対象を絞らないと、意図しない場面で連続ブロックの回数を使います。
サブエージェントのfrontmatterに書いたStopもSubagentStop扱いになる
サブエージェントの定義には、frontmatterに直接フックを書く方法もあります。ここに書いた Stop は、そのサブエージェントが起動されたときに自動で SubagentStop へ変換されます。通常の Stop 設定とは別物ですが、CLAUDE_CODE_STOP_HOOK_BLOCK_CAP の対象という点は同じです。
似た安全装置との違い
「回数で暴走を止める」環境変数やフックはほかにもあります。対象も既定値も異なります。
| 名称 | 対象 | 数えるもの | 既定値 | 0を入れたとき |
|---|---|---|---|---|
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP | 対象Stop / SubagentStop hook | 数えるもの連続ブロック回数 | 既定値8 | 0を入れたとき上限を無効化 |
CLAUDE_CODE_MAX_TURNS | 対象セッション全体 | 数えるものエージェントのターン数 | 既定値上限なし | 0を入れたとき起動時エラー |
| StopFailureフック | 対象APIエラーでの終了 | 数えるもの対象外(継続の仕組みを持たない) | 既定値— | 0を入れたとき— |
StopFailureフックは、出力も終了コードも無視されます(端末に出す terminalSequence だけは例外)。decision や additionalContext による継続の仕組みそのものがないので、上限の対象にもなりません。ログ送信や通知だけを行う用途に向きます。
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP と CLAUDE_CODE_MAX_TURNS は独立した上限です。前者はStop / SubagentStopフックが連続でブロックした回数に対する上限で、ターン数を数える CLAUDE_CODE_MAX_TURNS とは対象が別です。
上限を引き上げる前に見ておくこと
正当な理由で8回を超える構成はあります。1回のブロックでレポートの節を1つずつ埋めるフックや、前述のテスト駆動のフックがそうです。
ただし、毎回同じ理由で block を返し続けているなら、上限を上げても待たされる回数が増えるだけです。フックのスクリプトで reason をログファイルに書き残しておけば、何回目の継続で判定がどう変わったかを後から追えます。上限に達する前に、条件の書き間違いに気づけます。イベント一覧と設定方法はHooks完全ガイドにあります。
まとめ
既定の8回で困るのは、フックが正当に8回を超えて継続する構成のときだけです。上限を0にする前に、終了条件が現実に満たされるかを見直すほうが、無限ループを防ぐ目的に合っています。Claude CodeでTDDを回す手順では、Stop hookを使った継続の実例を扱っています。公式プラグインのralph-loopも、Stop hookで同じプロンプトを繰り返す例です。