Claude Codeのhooks同時実行は1イベント12件まで — prompt/agentの待ち順
v2.1.302でpromptフックとagentフックは、同じイベントで同時に走るのが最大12件になりました。残りは順番待ちです。待ちが出る構成と、枠を使い切らない設計を見ます。
Claude Code v2.1.302から、type: "prompt" と type: "agent" のフックは、同じイベントで同時に走るのが最大12件になりました。13件目以降は、先に走っているフックが終わるまで順番を待ちます。更新履歴の記述は「Changed prompt and agent hooks so that at most 12 of one event run at a time; the rest wait their turn」の1行だけです。
この上限はフック設定のリファレンスには載っていません。リファレンスにあるのは「一致するフックはすべて並列で走る」という説明です。更新履歴とリファレンスを突き合わせると、上限で変わる点、待ちが出る構成、枠を使い切らない作り方が見えてきます。
12件上限とフックの既定値
同時に走る上限
12件
同じイベントの prompt / agent フック
promptフックの既定timeout
30秒
agentフックは60秒
agentフックの最大ターン数
50
サブエージェントが調べ回れる上限
上限がかかるのは「同じイベントのpromptとagent」だけ
更新履歴の文面が指すのは、promptフックとagentフックです。シェルコマンドを走らせる command、HTTPに投げる http、MCPツールを呼ぶ mcp_tool の3種は、この1行には出てきません。
フックの種類を並べると、上限の対象が見えやすくなります。
| 種類 | 中身 | 更新履歴の12件上限 |
|---|---|---|
command | 中身シェルコマンドを実行 | 更新履歴の12件上限記載なし |
http | 中身URLへPOST | 更新履歴の12件上限記載なし |
mcp_tool | 中身MCPサーバーのツールを呼ぶ | 更新履歴の12件上限記載なし |
prompt | 中身モデルに1回だけ判断させる | 更新履歴の12件上限対象 |
agent | 中身サブエージェントが調べて判断 | 更新履歴の12件上限対象 |
もう1つの軸が「同じイベントで」という限定です。たとえば PreToolUse に10件、Stop に10件のpromptフックを置いても、どちらも12件以内に収まっています。数えるのはイベントごとで、設定ファイル全体の合計ではありません。
上限の仕組みは、設定の書き方を変えなくても効きます。上限を超えない構成なら、挙動は以前と同じです。影響を受けるのは、1つのイベントに多数のpromptフックやagentフックを並べていた構成だけです。
なぜpromptとagentにだけ枠が要るのか
2種類だけが対象になった理由は、更新履歴に書かれていません。ただ、リファレンスの記述から、この2種がほかと違う点は読み取れます。
- promptフックは、フックの入力JSONとプロンプトをモデルに送ります。既定では、Claude Codeがバックグラウンドの機能に使うモデルが使われます
- agentフックは、サブエージェントを起動します。Read・Grep・Globなどで調べ、最大50ターンののちに
okを返します - どちらも、モデルへのリクエストを伴います。シェルコマンドの起動と違い、応答待ちとトークンの消費が生じます
つまり12件の枠は、モデルへの同時リクエストとサブエージェントの同時起動を抑える枠として読めます。ここは推測の域を出ませんが、対象がこの2種に絞られている点とは整合します。
関連する修正も、過去の更新履歴に残っています。
- v2.1.274: Stopのpromptフックがブロックのたびにプロンプト全体を再送していたのを修正。繰り返しのブロックは500文字のラベルで条件を指す形になりました
- v2.1.300: agentフックが
modelを無視していたのを修正。haikuのようなエイリアスも解決されます
どちらも「promptとagentのフックは、モデルを呼ぶ分だけ重い」という前提の延長にあります。フック全体の整理はHooks完全ガイドにまとめています。
待ちが出るのはどんな構成か
12件を超えて(13件目から)初めて、待ちが生まれます。条件を数えてみます。
- 対象は同じイベントに登録された
prompt/agentフックです - そのうち、マッチャーや
ifの条件に一致して実際に起動するものが数えられます - 一致した数が12件以内なら、これまでどおり全部が同時に走ります
- 13件以上が一致したときだけ、超えた分が順番待ちに入ります
3つ目の「一致した数」が大事です。リファレンスによれば、if は PreToolUse や PostToolUse などのツールイベントでだけ評価されます。たとえば Bash(git *) のように絞れば、登録が20件あっても、そのコマンドで起動するのは数件に収まります。
最悪の待ち時間を見積もる
フックが全部同じ長さで終わると仮定して、簡単な計算をします。これは仮定を置いた試算で、実測値ではありません。
| 一致したagentフック | 1件の所要時間 | 上限なしの場合 | 12件上限の場合 |
|---|---|---|---|
| 12件 | 1件の所要時間40秒 | 上限なしの場合約40秒 | 12件上限の場合約40秒 |
| 15件 | 1件の所要時間40秒 | 上限なしの場合約40秒 | 12件上限の場合約80秒 |
| 25件 | 1件の所要時間40秒 | 上限なしの場合約40秒 | 12件上限の場合約120秒 |
12件を超えた分は、2巡目、3巡目に回ります。Stopのように「終わる前に必ず通る」イベントでは、この待ちがそのまま体感の遅さになります。
待っている間のtimeoutの扱いは、リファレンスにも更新履歴にも書かれていません。待ち時間がtimeoutに含まれるのか、フックが走り出してから数えるのかは、文書からは分かりません。timeoutぎりぎりで動かしているフックを多数並べる構成では、この点が不確かなまま運用することになります。
もう1つ、書かれていないことがあります。「12件」が1回の発火の中の数なのか、同じイベントが同時に複数回発火したときの合計なのか、という点です。リファレンスには、並列のツール呼び出しで PostToolUse が並行して発火する、との記述があります。そのため、並列ツール呼び出しが多い場面では、同じイベント名のフックが重なって走る可能性があります。数え方の詳細は、更新履歴の1行からは決められません。
枠を使い切らない設計の選択肢
上限を意識して構成を見直すなら、使える手は4つあります。
12件の枠を節約する手
条件を1つのプロンプトにまとめる
リファレンスの例には、1つのStopフックで3つの条件を確かめる書き方があります。判断が同じ種類なら、フックを分けず、1本のプロンプトに条件を並べます。
決まりきった判定はcommandに任せる
ファイルの拡張子やパスの確認は、シェルで足ります。モデルを呼ぶ必要がなく、12件の枠も使いません。
マッチャーとifで起動を絞る
同じツールイベントでも、対象のツールやコマンドに絞れば、一致する数が減ります。
軽いモデルを指定する
promptフックには
modelフィールドがあり、省略すればバックグラウンド用のモデルです。判断が単純なら軽いモデルで足ります。
条件をまとめた設定の形
次は、リファレンスのStopの例に沿った形です。完了条件を3つ並べて、promptフック1本で確かめます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Claudeが止まってよいか判断してください。入力: $ARGUMENTS\n1. 依頼されたタスクがすべて完了している\n2. 未対応のエラーがない\n3. 続きの作業が残っていない\n満たすなら {\"ok\": true}、満たさないなら {\"ok\": false, \"reason\": \"理由\"} を返してください。",
"timeout": 30
}
]
}
]
}
}3本に分けたときと比べて、モデルへのリクエストは1回で済みます。一方で、1本に詰めた分だけ、どの条件で止まったかは reason の文面頼みになります。条件ごとに別の担当者や別のログへ流したい場合は、分けるほうが扱いやすいです。この節の方針は、枠を食う数と読みやすさのどちらを優先するかで決まります。
手元の設定にいくつあるか数える
まず、イベントごとにpromptフックとagentフックの本数を数えます。jq があれば、次の1行で足ります。
jq '.hooks | to_entries[]
| {event: .key, n: [.value[].hooks[]
| select(.type=="prompt" or .type=="agent")] | length}' \
.claude/settings.json出力が n: 12 を超えるイベントがあれば、そのイベントで待ちが出る可能性があります。ただし、数えられるのはこのファイルの分だけです。ユーザー設定、プラグイン、スキルのフックも同じイベントに加わります。リファレンスによれば、同じハンドラが複数の設定ファイルにあれば1回だけ走りますが、プラグインやスキルが持つ同じハンドラの写しは別扱いです。
遅いのは枠待ちか、モデルか
Stopや UserPromptSubmit で「遅い」と感じたとき、原因の候補は3つあります。枠待ち、モデルの応答、agentフックの調査ターンです。順に切り分けると、手戻りが少なくなります。
フックが遅いときの確かめ順
- Q同じイベントで起動するprompt/agentフックは12件以下ですか?答えが12件を超える答えがはい
枠待ちが濃厚です。一致するフックを
ifやマッチャーで絞るか、条件を1本にまとめます。 - Q遅いフックはcommandまたはpromptフックですか?答えがagentフック答えがはい
agentフックは最大50ターン調べられるため、調査に時間がかかります。プロンプトで調べる範囲を絞り、
timeoutを見直します。
モデル応答そのものが遅い
model に軽いモデルを指定して、応答時間を縮めます。
commandフックの所要時間は、デバッグログで確かめられます。claude --debug で起動すると、ログは ~/.claude/debug/<session-id>.txt に書かれます。v2.1.296以降は、ツールイベントなどのcommandフックが終わるたびに Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms) のような行を残します。
この行が説明しているのはcommandフックです。promptフックとagentフックにも同じ行が出るかどうかは、リファレンスに記載がありません。モデルを呼ぶフックの待ちは、ログの行からは測れない可能性があります。
12件を超えるなら、フックの役割を分け直す
12件を超えるpromptフックが必要になる構成は、1つのイベントに判断を詰め込みすぎている可能性があります。実務で分け直すなら、次の基準が使えます。
- 止める判断(
PreToolUseやStop): ブロックの理由が重要なものだけをpromptフックにします。品質の確認は、ok: falseの理由をClaudeに返して修正させる流れが合います。流れの組み方はpromptフックでStopを判断させる記事にあります - 調べてから判断する検証: テストの実行結果のように、実際のファイルを見ないと決まらないものはagentフックに回します。agentフックでテストが通るまで止めない記事に手順があります
- 通知やログ: モデルの判断が要らないものは、commandやHTTPのフックに寄せます
サブエージェント側にも同時実行の上限があります。v2.1.217では、同時に走るサブエージェントに既定の上限が入りました。フックの12件とは別の枠ですが、並列を際限なく広げないという流れは同じ向きにあります。今回の変更はv2.1.302の更新の1項目です。
まとめ
12件の上限は、多くの設定では何も変えません。効くのは、同じイベントに一致するpromptフックとagentフックが13件以上になったときだけです。
先に確かめたいのは、自分の設定でイベントごとの本数が12件を超えていないかどうかです。超えていれば、条件を1本にまとめるか、判定の軽いものをcommandに移します。待ち時間とtimeoutの関係が文書にない以上、枠いっぱいまで並べる構成は避けておくのが無難です。