Claude Media
Claude Codeのhooks同時実行は1イベント12件まで — prompt/agentの待ち順

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

    サブエージェントが調べ回れる上限

v2.1.302の更新履歴とフックのリファレンスから

上限がかかるのは「同じイベントの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件目から)初めて、待ちが生まれます。条件を数えてみます。

  1. 対象は同じイベントに登録された prompt / agent フックです
  2. そのうち、マッチャーや if の条件に一致して実際に起動するものが数えられます
  3. 一致した数が12件以内なら、これまでどおり全部が同時に走ります
  4. 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フックの調査ターンです。順に切り分けると、手戻りが少なくなります。

切り分け

フックが遅いときの確かめ順

  1. Q同じイベントで起動するprompt/agentフックは12件以下ですか?
    答えが12件を超える

    枠待ちが濃厚です。一致するフックを if やマッチャーで絞るか、条件を1本にまとめます。

    答えがはい
  2. 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の関係が文書にない以上、枠いっぱいまで並べる構成は避けておくのが無難です。

この記事を共有:XはてブLinkedIn