Claude Codeのワークフローでエージェントが止まったとき — 再試行と上限待ち
ダイナミックワークフローでエージェントが止まると最大5回再起動し、6回目で失敗します。stallMsで窓を伸ばす場面と効かない場面、使用量上限での一時停止の条件をまとめます。
ダイナミックワークフローの実行中、/workflowsでエージェント名に(retry 1)が付いたら、そのエージェントは出力が途絶えて自動で再起動されています。何もしなくて構いません。ただし再起動には上限があり、6回目の試行まで止まるとagent()の呼び出しが失敗します。
この記事は、再起動の仕組み、失敗時の3種類のメッセージの読み分け、stallMsで待ち時間を伸ばせる場面と伸ばしても効かない場面、そして使用量上限に当たったときの一時停止の条件をまとめます。ワークフロー自体の概要はダイナミックワークフローの解説にあります。
止まったエージェントは同じプロンプトから最大5回やり直される
エージェントの出力が一定時間届かなくなると、Claude Codeはその試行を打ち切り、同じプロンプトで始め直します。/workflowsでは名前に(retry 1)が付き、詳細にattempt 2 (stalled)と出ます。
やり直しの前後で、残るものと消えるものは次のとおりです。
再起動で引き継がれるもの・引き継がれないもの
引き継がれない
止まった試行の会話履歴です。新しい試行は白紙の状態でプロンプトから始まります。
残る
止まった試行がすでに書き換えたファイルと、使ったトークンです。トークンは実行全体の合計に積み上がります。
ファイルの変更が残るので、「途中まで書き換えて止まった」エージェントの再試行は、書き換え済みの状態から始まります。プロンプトに「すでに変更済みでも冪等に進める」書き方が要るかどうかは、スクリプトを読んで判断するところです。
待ち時間(ストールの窓)には、エージェントが自分のツール呼び出しの結果を待っている時間と、使用量上限のリセット待ちの時間は含まれません。長いビルドやテストをエージェントに走らせても、それだけでストール扱いにはなりません。
再起動は最大5回で、手動でrキーを押して再起動した分もこの回数に数えます。6回目も止まると失敗です。
失敗メッセージは3種類ある — 先頭の文言で原因を分ける
6回目の試行まで終わったagent()は、エラーの先頭に理由を出します。
| エラーの先頭 | 何が起きたか | 窓を伸ばして直るか |
|---|---|---|
agent stalled on all 6 attempts | 何が起きたか6回とも、窓いっぱいまで出力がなかった | 窓を伸ばして直るか直る可能性がある |
agent lost its reply on all 6 attempts | 何が起きたか6回とも応答ストリームが無音になり、待つのを諦めた | 窓を伸ばして直るか直らない |
agent abandoned after 6 attempts | 何が起きたか試行ごとに終わり方が違った | 窓を伸ばして直るかエラーに並ぶ順序を見て判断する |
3つ目は、たとえば1回目が無出力、2回目が応答の途絶、というように終わり方が混ざったときです。エラー本文に各試行の結末が順に並ぶので、同じ原因が続いているかを確かめます。
失敗したエージェントの切り分け
- Qエラーの先頭は agent stalled on all 6 attempts ですか?答えが次の問いへ答えがはい
エージェントの作業自体が長く黙っている状態です。
stallMsか環境変数で窓を伸ばし、ワークフローを再起動します。 - Q先頭は agent lost its reply on all 6 attempts ですか?答えが次の問いへ答えがはい
窓は関係ありません。ストリーミングの無音検知(ウォッチドッグ)側の設定と、通信経路を確認します。
- Q先頭は agent abandoned after 6 attempts ですか?答えがエラーの文言を再確認する
エラーに並ぶ各試行の結末を順に読み、多数派の原因に合わせて上の2つのどちらかへ進みます。
stallMsで窓を伸ばす — 1つのエージェントだけ、または全エージェント
agent stalled on all 6 attemptsが出るのは、エージェントの仕事の性質上、長く出力しない場合です。窓を伸ばす方法は2つあります。
- 1つのエージェントだけ:
agent()の呼び出しにstallMsをミリ秒で渡す - すべてのエージェント: 環境変数
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSを設定する。ワークフローのエージェントに効くのはClaude Code v2.1.286以降で、ワークフロー外のサブエージェントにも同じ変数が効く
スクリプトでの書き方は、たとえば次の形です(30分の例)。
const report = await agent(
'Run the full integration suite and summarize failures.',
{ stallMs: 1800000, label: 'integration-suite' },
)環境変数は、起動前にシェルで指定します。
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS=1800000 claude環境変数の既定値は600000(10分)です。ストリーミングの無音検知を延ばしたときに既定値も連動して延びる点など、変数そのものの挙動はCLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSの解説にまとめています。
使い分けは単純で、長く黙るエージェントが特定の1つだけならstallMs、スクリプト全体で似た性質の仕事が並ぶなら環境変数です。全体を伸ばすと、本当に止まったエージェントの検知も遅れます。
agent lost its replyは窓を伸ばしても直らない
agent lost its reply on all 6 attemptsの原因は、ストール窓ではありません。応答のストリームが無音になった時点で、ストリーミングのアイドルウォッチドッグが先に応答を打ち切っています。ストールの窓をいくら伸ばしても、ウォッチドッグのほうが先に働くため結果は変わりません。
調整するのはウォッチドッグ側で、タイムアウトは環境変数CLAUDE_STREAM_IDLE_TIMEOUT_MSが決めます。ネットワーク設定のページによると、ウォッチドッグは4つの独立したタイマーで構成されます。
直接のAnthropic APIでの既定の無音タイムアウト
最初のバイトまで
180秒
リクエスト送信後、応答ヘッダーが届かない
バイト単位の無音
180秒
keep-aliveのpingを含め、1バイトも届かない
イベント単位の無音
300秒
応答のイベントが1つも解釈できない
CLAUDE_STREAM_IDLE_TIMEOUT_MSを明示的に設定する場合、最小値は300000(5分)で、それより小さい値は黙って5分に引き上げられます。バイト単位のウォッチドッグに限っては、上限が30分です。プロキシ越しで応答が詰まる環境では、この変数を伸ばす対処が候補になります。設定の詳細はClaude Codeの応答が止まったときのタイムアウト調整で扱っています。
整理すると、同じ「止まった」でも次のように担当が分かれます。
止まり方ごとの調整先
作業が長く黙る
stallMsまたはCLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSで、ストールの窓を伸ばします。応答の途中で無音になる
CLAUDE_STREAM_IDLE_TIMEOUT_MSなどのウォッチドッグ側を見ます。ストールの窓は触りません。
失敗したあと、実行が続くかはスクリプトの書き方で決まる
6回とも止まったagent()の扱いは、呼び出し方で2つに分かれます。
parallel()またはpipeline()の内側: 実行は続き、そのエージェントの結果の位置にnullが入る- 直接
awaitしている: 実行がそのエラーで終わる
pipeline()はnullをそのまま結果配列に残します。保存されたスクリプトの例が末尾に.filter(Boolean)を置いているのは、停止したエージェントや、全試行でストールしたエージェントの枠を除くためです。
自分でスクリプトを書き換えるなら、扇形に広げた処理の結果を使う前にnullの混入を前提にしておくと、1体の失敗で後段が壊れません。
const audits = await pipeline(files, f =>
agent(`Audit ${f} for missing auth checks.`, { label: f, stallMs: 1200000 }),
)
const done = audits.filter(Boolean)
const missed = audits.length - done.length // 全試行で止まった数
return { done, missed }失敗した分だけをやり直したいときは、Claudeにワークフローの再起動を頼みます。どのエージェントが再実行されるかは、次の「再開」の節の規則に従います。
再開すると、失敗以降のエージェントがまとめてやり直しになる
一時停止した実行は、/workflowsで選んでpキーを押すと再開できます。pは一時停止と再開を切り替えるキーで、x(選択中のエージェントまたは実行全体を止める)やr(選択中の実行中エージェントを再起動する)と並んで画面下部のフッターに出ます。
再開時、Claude Codeはエージェントが起動した順に実行を再生し、各エージェントは保存済みの結果を返すか、もう一度走ります。
- 完了済み: 保存された結果を返す。ただし、プロンプトが前回と変わった最初のエージェントから先は、完了済みでも再実行される
- 停止時に実行中だった: 最初からやり直し。実行全体を止めた場合、失敗とは数えない
- 失敗した: 再実行され、それより後に起動したエージェントも、完了していたものを含めて再実行される
最後の規則は、扇形の途中の失敗が完了済みの仕事を巻き込むことを意味します。A・B・C・Dの順に起動してBが失敗すると、再起動ではAだけキャッシュから返り、B・C・Dが走り直します。stallの失敗もこの「失敗」に当たるため、窓の見積もりが甘いと、再開のたびに高くつきます。1つのエージェントを/workflowsで選んでxで止めた場合も失敗として数えられる点にも注意が要ります。
使用量上限に当たると失敗せず、一時停止して待つ
claude.aiの使用量上限にエージェントが当たったとき、v2.1.271以降の実行は、そのエージェントを失敗にせず一時停止します。上限に当たったエージェントはリセットを待ち、新しいエージェントは始まりません。リセットの少し後に待っていたエージェントが走り出し、実行は自動で続きます。待機中は、タスクパネルの進捗行と/workflowsのヘッダーにリセット時刻が出ます。v2.1.271より前では、該当のエージェントは失敗します。
一時停止するのは、次の4条件がそろったときだけです。1つでも欠けると、そのエージェントは失敗します。
上限待ちで一時停止する4条件
- 1
対話セッションで、claude.aiにサインインしている
claude -p・Agent SDK・バックグラウンドセッション・Remote Controlやエージェントチームのチームメイトのセッションでは、一時停止しません。 - 2
autoContinueAtUsageLimitがオン
セッション自体が上限リセットを待つのと同じ設定です。待機中にオフにすると、待っていたエージェントは失敗します。
- 3
リセットが24時間以内
週次の上限は、これより先になることがあります。
- 4
その実行で待った回数が2回以内
3回目に上限へ当たると、エージェントは失敗します。
autoContinueAtUsageLimitは、セッションの自動待機を制御する設定項目です。対話セッションの自動待機そのものはv2.1.234以降で、claude.aiのサブスクリプションでサインインした対話セッションでは既定でオンです。オフにするならsettings.jsonに次の1行を足します。
{
"autoContinueAtUsageLimit": false
}待ちの途中でオフにすると、ワークフロー側の待機も終わって該当エージェントが失敗するので、夜間に回す実行ではオンのまま残す前提で考えることになります。停止と再開の画面操作は/workflowsの解説にもあります。
stallの窓に、上限待ちの時間は数えられない
「上限待ちの間にstall扱いで再起動されるのでは」という疑問には、ストールの窓の定義が答えています。窓に含まれないのは、エージェントが自分のツール呼び出しを待つ時間と、使用量上限のリセット待ちの時間です。上限で止まっている間は、無出力の時間として加算されません。
上限待ちの回数制限(2回)とstallの再起動(5回)は、別の仕組みの別の上限です。
運用でつまずきやすい点
stallMsを伸ばしすぎる: 本当に止まったエージェントの検知も遅れ、その間は枠を占有します。長く黙る理由が分かっているエージェントにだけ指定します- 窓を伸ばしても
lost its replyが消えない: 先頭の文言を見直します。ウォッチドッグ側の問題です claude -pで夜間に回して上限に当たる: 非対話モードでは一時停止せず、エージェントが失敗します。対話セッションで走らせるか、失敗後の再起動を前提にします- 変更済みのファイルを前提にしないプロンプト: 再試行は履歴なしで始まりますが、ファイルの変更は残ります。「ファイルがすでに修正されていても壊さない」指示が要る場合があります
まとめ
エージェントの停止は、再起動で自動回復する層(最大5回)と、6回目でagent()が失敗する層に分かれます。失敗時は、エラーの先頭がstalledならstallMsかCLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS、lost its replyならCLAUDE_STREAM_IDLE_TIMEOUT_MS側、と調整先が変わります。失敗後に実行が続くかはparallel()・pipeline()の内側にあるかどうかで決まり、続く場合は結果の位置がnullになります。
使用量上限は、対話セッションでclaude.aiにサインインしていてautoContinueAtUsageLimitがオンなら、失敗ではなく一時停止になります。非対話のclaude -pや24時間以上先の週次リセットは対象外です。長い実行を任せる前に、どの経路で走らせるかを決めておくと、止まったときの切り分けが速くなります。