Claude Media
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSとは — サブエージェントのストール検知の設定

CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSとは — サブエージェントのストール検知の設定

CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSは、進捗のないサブエージェントを強制終了する環境変数です。既定値の連動先、中断後の挙動、他の制御との使い分けをまとめます。

CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS は、サブエージェントの「進捗なし」を判定するミリ秒数を変える環境変数です。既定は600000(10分)。ストリーミングイベントが一定時間届かないと、Claude Codeはそのサブエージェントを強制終了し、親セッションへストールを報告します。バックグラウンドサブエージェントではタスクが失敗として記録され、途中までの出力も一緒に残ります。ストールとは別に、非同期サブエージェントの完了通知がストリーミングモードで拒否される既知のバグもあり、このエラーの原因にまとめています。

この変数が見ているのは「時間」ではなく「イベントの間隔」

判定の単位は、経過時間の合計ではなくストリーミングイベントの間隔です。サブエージェントがトークンを吐き続けている限り、実行が何分続いてもタイマーはその都度リセットされます。逆に、1つのイベントも来ないままCLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSの時間が経過すると、そこで強制終了されます。

似た名前の制御がいくつかあるため、何を測っているかで並べると区別しやすくなります。

変数 / フィールド検知条件対象範囲
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS検知条件ストリーミングイベントが一定時間来ない対象範囲サブエージェント単位
maxTurns(サブエージェント定義)検知条件ターン数が上限を超える対象範囲サブエージェント単位
API_TIMEOUT_MS検知条件1リクエストが時間内に終わらない対象範囲メインループ + 全サブエージェント
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS検知条件同時実行数が上限を超える対象範囲セッション単位

API_TIMEOUT_MSは1回のAPIリクエストの制限時間で、既定は同じ600000(10分)ですが、メインの会話にもすべてのサブエージェントにも一律にかかります。一方CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSはサブエージェント専用で、リクエスト1本の長さではなく「動いている気配があるか」だけを見ます。長い1回のリクエストを許容しつつ、完全に応答が止まったケースだけを狙って検知したいときに使う変数です。

既定値はストリーミングウォッチドッグと連動する(v2.1.257から)

CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSの既定値は固定ではありません。ストリーミングアイドルウォッチドッグ(CLAUDE_ENABLE_STREAM_WATCHDOG。全プロバイダーで既定オン)が有効な間は、既定値がCLAUDE_STREAM_IDLE_TIMEOUT_MSに5分を足した値になります。CLAUDE_STREAM_IDLE_TIMEOUT_MSの既定は300000ミリ秒(5分)なので、通常は5分+5分で600000です。ウォッチドッグをオフにしている場合は、連動を外れて既定600000に固定されます。

v2.1.257より前は、この連動そのものが無く、既定値は常に600000固定でした。CLAUDE_STREAM_IDLE_TIMEOUT_MSをどれだけ伸ばしても、サブエージェントのストール検知は10分のままだったわけです。

短くしたいときは、この変数を直接指定する

検知を早めたいとき、CLAUDE_STREAM_IDLE_TIMEOUT_MSを短くしても効きません。CLAUDE_STREAM_IDLE_TIMEOUT_MSを明示的に設定した場合の下限は300000で、それより小さい値は黙って300000に引き上げられます。拡張思考中の沈黙やプロキシのバッファリングで誤って切らないための下限です。

連動で決まる既定値は、下限の300000に5分を足した600000を下回りません。5分より短くするなら、この変数に値を直接指定します。Agent SDKの公式例も、API_TIMEOUT_MSとあわせて120000(2分)を指定しています。

ストリーム全体を見るウォッチドッグは、サブエージェントに限らずすべてのモデル応答に働く別系統の仕組みです。直接のAnthropic APIでの既定の秒数は、公式のネットワーク設定ページで次のように並んでいます。

数字

ウォッチドッグとストール検知の既定値

  • バイト単位の無音

    180秒

    keep-aliveのpingも含め、1バイトも届かない

  • イベント単位の無音

    300秒

    応答のイベントが1つも解釈できない

直接のAnthropic APIで接続した場合。ゲートウェイやクラウド経由では値が変わるものがあります

ウォッチドッグがリクエストを切ると、Claude Codeはそれを途中失敗として扱い、再試行するか、不完全な応答の注記つきでターンを続けるか、エラーでターンを終えるかを進み具合で決めます。サブエージェントの応答が途中で切れたときは、テキストを含んでツール呼び出しが無ければ、続きを促すプロンプトがまず送られます。ストール検知はそのさらに外側で、サブエージェント全体を止める仕組みです。

強制終了後に何が起きるか — フォアグラウンドとバックグラウンドで扱いが違う

タイマーが切れたあとの挙動は、そのサブエージェントがフォアグラウンドとバックグラウンドのどちらで動いていたかで変わります。

くらべる

ストールで中断されたあとの扱い

中断と報告

フォアグラウンド

サブエージェントを中断し、ストールが起きたことを親セッションへ報告します。

失敗として記録

バックグラウンド

中断に加えてタスクが失敗として記録され、通知には途中までの結果(partial result)が添えられます。途中まで進んだ作業を失わないための仕組みです。

ストールで失敗したサブエージェントは、プロンプト下のパネルに30秒間残ります。xキーを押せばその場で行を消せます。ただし、これは/tasksの一覧には乗りません。成功終了したものは完了後30秒だけ一覧に残りますが、失敗したものや止めたものは、その時点で一覧から外れ、パネルの行からだけ確認できる非対称な扱いです。詳細はClaude Codeの/tasksコマンドでサブエージェントの完了を確認するにまとめています。

フォアグラウンドとバックグラウンドはどちらで動くか

サブエージェントがどちらで動くかは、毎回の指定ではなく、次の条件を上から順に当てて決まります。

  1. agent teamsのうち、プロセス内で動くteammateがAgentツールで起動したサブエージェントは、常にフォアグラウンドです。サブエージェント定義にbackground: trueがあると、Claude Codeはエラーで起動を拒否します
  2. CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1を設定していれば、セッションの種類やfork modeにかかわらずフォアグラウンドです
  3. fork modeがオンなら、フォークかどうかを問わずバックグラウンドで動き、Claudeはフォアグラウンドを選べません。対話セッションではfork modeが既定でオンです
  4. fork modeがオフなら、Claudeは基本バックグラウンドで動かし、結果をすぐ使いたいときだけフォアグラウンドを選びます。非対話モード(-p)とAgent SDKでは既定でオフです

サブエージェント定義のfrontmatterにbackground: trueを書くと、Claudeが結果を先に必要としていても、そのサブエージェントだけバックグラウンドに固定できます。対話的に使っている限り、ストールで中断されたサブエージェントはバックグラウンドの経路をたどるのが既定の動きです。

ストールしたサブエージェントのその後

バックグラウンドでストールしたサブエージェントは、次の流れをたどります。

手順

再開するときの流れ

  1. 1

    親に結果が届く

    ストールの報告と、途中までの出力が親セッションへ渡ります。

  2. 2

    Claudeが再開を依頼する

    ClaudeがSendMessageツールにエージェントIDかnameを渡すと、新しいAgent呼び出しなしでバックグラウンドで再開します。

  3. 3

    元の状態から続く

    再開した実行は元のツールセットを引き継ぎ、元の実行が温めたプロンプトキャッシュも読み続けられます。

公式が再開できると書いているのは、完了したサブエージェントと、TaskStopツールで止めたサブエージェントです。APIエラーで失敗した場合も、原因が解消したら「再開を依頼する」流れが案内されています。ストール失敗そのものを名指しした記述はないので、同じ手順を試す位置づけになります。

再開が拒否されるパターンもあります。v2.1.199以降、SendMessageは指定された名前が会話中で以前に到達したのと同じエージェントを指しているかを確認します。同じ名前の別のバックグラウンドサブエージェントが後から起動していると、Claude Codeは誤った相手に送らないよう送信を拒否し、その名前が今どのエージェントを指すかをエラーで返します。以前のエージェントへ届けるには、Claudeが起動時に受け取ったエージェントIDで名指しします。この確認は会話の中だけで有効で、/clearで解除されます。

ユーザー自身が/tasksでxを押して止めたサブエージェントは、自動では再開しません。Claudeがメッセージを送っても拒否されます。失敗や完了済みのサブエージェントを再開すると、タスク一覧では実行中の表示に戻ります(v2.1.205以降)。

サブエージェント数・深さの制限と組み合わせる

CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSは「1つのサブエージェントが止まっていないか」を見る変数で、「いくつまで同時に動かせるか」を見る変数とは役割が分かれています。並列実行を前提としたワークフローでは、両方を目的に応じて調整することになります。ワークフロー側の再起動回数と失敗の読み分けは止まったエージェントの再試行と上限待ちにあります。

目的触る変数
個々のサブエージェントの応答停止を早く検知したい触る変数CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSを短くする
同時に動かせるサブエージェント数を増減したい触る変数CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS
個々のサブエージェントに割り当てるモデルを固定したい触る変数CLAUDE_CODE_SUBAGENT_MODEL

同時実行数の既定値(20)や、子サブエージェントを持てる階層の上限はCLAUDE_CODE_MAX_CONCURRENT_SUBAGENTSとはで扱っています。モデル固定の仕組みはCLAUDE_CODE_SUBAGENT_MODELとはを参照してください。同時実行数の上限は、ultracodeが有効なセッションでは適用されません。また、完了済みのサブエージェントを再開するときは、上限を確認せずに新しい枠を1つ使います。通常は、止まったままのサブエージェントが枠を埋めていると、Agentツールは新しい起動を拒否します。大量に並列で回すワークフローでは、CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSを短めに指定して早めに手放す運用と噛み合います。

なぜサブエージェントだけに自動アボートの仕組みが要るのか

メインの会話でツール呼び出しが長引いているときは、Claude Codeが30秒おきにtool_progressという経過報告(ハートビート)を流し、長時間実行中なのか止まっているのかを区別できます。この経過報告は、サブエージェント内部のツール呼び出しには流れません。SDK側から見ると、サブエージェントが作業を続けているのか内部で止まっているのかを判別する手がかりがありません。

CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSが自動でアボートまで行うのは、この見えなさを埋める役割を担っているためと言えそうです。ハートビートのフィールドはAgent SDK v0.3.214以降で使えます。v2.1.257より前は、フォアグラウンドのAgentツール呼び出しにも流れていませんでした。

設定のしかた

シェルの環境変数として渡します。

export CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS=120000
claude

プロジェクトで固定値を共有したい場合はsettings.jsonのenvキーに書きます。

{
  "env": {
    "CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS": "120000"
  }
}

Agent SDKからCLIサブプロセスを起動する場合は、queryのenvオプションに渡します。process.envを丸ごと置き換える仕様なので、PATHなどを引き継ぐには展開した上で追加します。

import { query } from "@anthropic-ai/claude-agent-sdk";
 
const result = query({
  prompt: "Analyze this code",
  options: {
    env: {
      ...process.env,
      CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS: "120000",
    },
  },
});

よくあるつまずき

  • CLAUDE_STREAM_IDLE_TIMEOUT_MSだけ伸ばして満足してしまう。v2.1.257以降は既定値が連動するため、意図せずCLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS側の検知時間まで一緒に伸びている
  • CLAUDE_STREAM_IDLE_TIMEOUT_MSを短くして検知を早めたつもりになる。明示指定の下限は300000で、それ未満は引き上げられる
  • 無限ループのような「終わらないサブエージェント」を止める変数だと誤解する。トークンを出力し続けているならタイマーはリセットされ続けるため、ストール検知では止まらない。ターン数で打ち切りたいならmaxTurnsが対象
  • メインセッションが固まったときにもこの変数が効くと思い込む。対象はあくまでサブエージェントで、メインループの制御はAPI_TIMEOUT_MSが担う
  • アボートされたバックグラウンドサブエージェントが自動で再開すると思い込む。自動では再開せず、ClaudeがSendMessageで呼びかけるか、パネルに行が残っている間にユーザーが文字起こしへ入力する操作が要る

まとめ

サブエージェントの検知を5分より早めたいときに触るのはCLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSだけです。CLAUDE_STREAM_IDLE_TIMEOUT_MSは5分より下げられず、上げると既定のストール検知まで遅くなります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/env-vars
  2. code.claude.com/docs/en/agent-sdk/typescript
  3. code.claude.com/docs/en/sub-agents
  4. code.claude.com/docs/en/network-config
  5. code.claude.com/docs/en/errors
この記事を共有:XはてブLinkedIn