Claude Codeのretry watchdog待機上限をMAX_WAIT_MSで決める
CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MSは、無人セッションが429と529を待ち続ける時間をリクエストごとに区切る環境変数です。値の決め方とCIでの置き方、近い変数との違いを書きます。
CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MSは、CLAUDE_CODE_RETRY_WATCHDOGを設定した無人セッションが429と529を待ち続ける時間に、リクエスト単位の上限を付ける環境変数です。ミリ秒の正の整数で指定します。未設定なら待機に上限はありません。Claude Code v2.1.295で加わりました。
CIジョブや評価ハーネスで「待ち続けてジョブが終わらない」を避けたいときの、最後の歯止めにあたります。
RETRY_WATCHDOGの待機は何が終わらせるのか
Claude Codeは、失敗したAPIリクエストを既定で最大10回までリトライします。CLAUDE_CODE_RETRY_WATCHDOG=1を設定すると、429と529のキャパシティエラーに限って、この回数制限が外れます。人が張り付いていない環境で、障害や混雑が明けるまで粘るための設定です。
待機の間隔は最大5分までバックオフし、レスポンスにレート制限のリセット時刻があればその時刻まで待ちます。支出上限や使用クレジット切れを知らせる429だけは、待たずにすぐ失敗します(v2.1.239以降)。待機ルールの全体はretry exhaustionの検知手順にあります。
待機を終わらせる要因は、従来は2つしかありませんでした。待っていた状況が解消するか、人がジョブを止めるかです。529が数時間続けば、ジョブも数時間待ちます。
この変数は、そこに3つ目の終わり方を足します。
上限を使い切ると何が起きるか
仕様は短く、次の3点です。
| 項目 | 内容 |
|---|---|
| 対象 | 内容CLAUDE_CODE_RETRY_WATCHDOGが設定されているときの429と529の待機 |
| 単位 | 内容1つのAPIリクエストごと(セッション全体や1ターン全体ではない) |
| 超過後 | 内容時間を使い切ったあとに次の429か529が来ると、そのリクエストが終わる |
注意したいのは「超過後」の挙動です。上限に達した瞬間にリクエストが打ち切られるわけではありません。使い切ったあとで、次の429か529を受けたときに終わります。待っている最中のスリープが途中で切られるかどうかは、ドキュメントに記載がありません。
また、リクエストが終わったあとにエラーとして何が表示されるか、その文言も記載がありません。スクリプトで文字列を拾って分岐させる設計は避け、OTelのapi_errorイベントで見る形にしておくと安全です。終了コードにどう出るかも記載がないので、ジョブ側で実際に失敗したときの値を一度確かめておく必要があります。
CLAUDE_CODE_RETRY_WATCHDOGを設定せずにこの変数だけを置いたときの動作も、記載がありません。2つはセットで入れるものとして扱ってください。
値はどう決めるか
値は1800000(30分)のように、桁を数えて書きます。換算を間違えやすいので、よく使う値を並べます。
| 待機の上限 | 指定する値 |
|---|---|
| 10分 | 指定する値600000 |
| 30分 | 指定する値1800000 |
| 1時間 | 指定する値3600000 |
| 3時間 | 指定する値10800000 |
3時間は、CLAUDE_CODE_RETRY_WATCHDOGが429・529以外の一過性エラーに使う既定の300回(バックオフにしておよそ3時間)と同じ桁です。上限の目安としては、ここが一つの区切りになります。
決め方の軸は、待機が終わったあとで、ジョブ側が何をするかです。
- 次の実行で取り返せるジョブ(夜間のバッチ、定期レビュー)は、10〜30分で切って失敗にする。再実行の仕組みがあるなら、そちらに任せたほうが状況を見直しやすい
- 途中までの成果を捨てたくないジョブ(長い移行作業)は、1時間以上にして粘る。そのかわりCI側のタイムアウトで二重に守る
- 429の原因が自分たちの並列数にある場合は、待ち時間を伸ばしても解決しない。並列数を減らす側を先に見る
リクエスト単位の上限だと、ジョブ全体は守れない
上限は1リクエストごとに数えられます。ジョブの中でモデルを呼ぶ回数は多いので、30分に設定しても、ジョブ全体で30分に収まる保証にはなりません。529が断続的に続くと、リクエストのたびに最大30分ずつ待つ可能性があります。
ジョブ全体の長さは、CIのtimeout-minutesのような外側の仕組みで別に縛る必要があります。この変数の役目は、1回の障害に1つのリクエストが飲み込まれ続けるのを防ぐことです。
最悪値を見積もる
ジョブの最大待機時間は、上限値に「429か529で待たされるリクエストの数」を掛けた値が目安になります。たとえば上限を15分にして、障害の間に3つのリクエストが順に待たされるなら、待機だけで最長45分です。timeout-minutesを60分にしたジョブなら、この3回の待機で枠の4分の3を使います。
上限を決めるときは、次の2つを並べて見ます。
| ジョブの性格 | MAX_WAIT_MSの置き方 | 外側の枠 |
|---|---|---|
| 定期レビュー、夜間バッチ | MAX_WAIT_MSの置き方10〜15分 | 外側の枠timeout-minutesは上限の3〜4倍が収まる範囲 |
| 長い移行、評価ハーネス | MAX_WAIT_MSの置き方1時間前後 | 外側の枠ジョブ全体の期限を別に決める |
| 並列実行が多いワークフロー | MAX_WAIT_MSの置き方短めにして、先に並列数を見直す | 外側の枠同時に走る本数ぶんの待機が重なる点に注意 |
上限はリクエストごとに数えるため、並列で走る各セッションがそれぞれ上限まで待てます。待機時間の合計ではなく、いちばん長く待つ1本が全体の終わりを決める点も、見積もりの前提に入れてください。
CIへの置き方
GitHub Actionsなら、ジョブのenvに2つの変数を並べます。ここではジョブ全体を60分で止め、1リクエストの待機は15分で切る構成です。
jobs:
nightly-review:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
CLAUDE_CODE_RETRY_WATCHDOG: "1"
CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS: "900000"
steps:
- uses: actions/checkout@v4
- run: claude -p "直近のコミットをレビューして要点を書き出して"claudeの導入手順は省いています。連携の全体像はGitHub Actions連携の解説を参照してください。
シェルから起動するならexportで足ります。
export CLAUDE_CODE_RETRY_WATCHDOG=1
export CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS=1800000
claude -p "テストを実行して失敗を直して"settings.jsonのenvにも書けます。プロジェクトの.claude/settings.jsonに入れるとチームの全員に効き、.claude/settings.local.jsonなら自分だけです。
{
"env": {
"CLAUDE_CODE_RETRY_WATCHDOG": "1",
"CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS": "1800000"
}
}ただし、開発者が手元で対話的に使う環境にまでこれを配ると、人が見ている画面で待ち続けるセッションが増えます。無人ジョブ専用の設定ファイルか、ジョブのenvに限定したほうが事故は少なくなります。
この変数が止めないもの
効く範囲は429と529の待機だけです。同じwatchdogの下でも、サーバーエラー・タイムアウト・接続断のような一過性エラーは別の数え方になります。v2.1.199以降は再試行が既定で300回、バックオフにしておよそ3時間分あり、MAX_WAIT_MSを小さくしても短くなりません。
つまり、MAX_WAIT_MSを15分にしたジョブでも、500番台や接続断が続けば、そのぶん別の理由で粘ります。この種の失敗を早く表に出したいときは、CLAUDE_CODE_MAX_RETRIESを小さく置く手があります。ただしwatchdogを設定していると、watchdogが回数の既定を押し上げたり上限の15を外したりするので、両方を使うなら挙動を一度ジョブで確かめてください。
ストリーミングの応答ヘッダーが返らない場合も同様です。初回バイトの期限が働く接続では、再送は1モデルリクエストにつき1回までですが、CLAUDE_CODE_RETRY_WATCHDOGを設定するとその1回の制限は外れます。この再送にもMAX_WAIT_MSは関係しません。
待機中の画面で何が見えるか
待機や再試行の最中、スピナーにはRetrying in Ns · attempt x/yのカウントダウンが出ます。原因を示すラベルは、対処できる失敗なら最初の試行から、それ以外はAPI errorと出たあと3回目の試行から具体名に切り替わります(v2.1.198以降)。529のときは、カウントダウンの下にstatus.claude.comなどの確認先も表示されます。
対話セッションで人が見ているぶんには、これで状況がわかります。無人ジョブではこの画面を誰も見ないので、MAX_WAIT_MSで出口を決めておくことと、ジョブのログに残る形で失敗を拾える仕組みが、見張りの代わりになります。
近い名前の変数との違い
リトライ周りの変数は数が多く、効く範囲が重ならないものがあります。この変数が受け持つのは429・529の待機時間だけです。
| 変数 | 何を決めるか | 備考 |
|---|---|---|
CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS | 何を決めるか429・529の待機時間の上限(リクエストごと) | 備考watchdog設定時のみ。v2.1.295以降 |
CLAUDE_CODE_MAX_RETRIES | 何を決めるかリトライ回数(既定10) | 備考watchdog設定時は上限の15が外れる |
CLAUDE_CODE_OVERLOADED_RETRY_BASE_DELAY_MS | 何を決めるか529の指数バックオフの初期遅延 | 備考watchdogを1にすると効かない |
CLAUDE_CODE_NONSTREAMING_TIMEOUT_RETRIES | 何を決めるか非ストリーミングのタイムアウト再送回数 | 備考v2.1.285以降 |
API_TIMEOUT_MS | 何を決めるか1リクエストのタイムアウト(既定600000) | 備考待機ではなく通信そのものの長さ |
CLAUDE_CODE_OVERLOADED_RETRY_BASE_DELAY_MSは、watchdogを有効にした環境では効きません。watchdogの下で529の間隔を調整したいという用途には使えない点に注意が必要です。
ストリーミングが途中で止まる問題は別の仕組みです。こちらを調整したい場合は応答が止まったときのタイムアウト調整に原因別の対処をまとめています。
watchdogはバージョンごとに待機の出口を増やしてきた
無人セッションの再試行は、バージョンを重ねて少しずつ出口を増やしてきました。
| バージョン | 変更 |
|---|---|
| v2.1.186 | 変更CLAUDE_CODE_MAX_RETRIESの上限が15になり、無人セッションはCLAUDE_CODE_RETRY_WATCHDOGを使う形になった |
| v2.1.199 | 変更watchdog下で一過性エラーの既定が300回になり、15回の上限が外れた |
| v2.1.239 | 変更支出上限や使用クレジット切れの429は、待たずにすぐ失敗するようになった |
| v2.1.281 | 変更429・529の待機が続いたあとの最初の5xxや接続断で失敗してしまう問題と、5xxの長いRetry-Afterで無音のまま上限なく眠る問題が修正された |
| v2.1.288 | 変更非常に長い応答ストリームの失敗で何時間も再試行する問題が直り、3回のタイムアウトで諦めるようになった |
| v2.1.295 | 変更429・529の待機にリクエスト単位の上限を置けるMAX_WAIT_MSが加わった |
流れとして、v2.1.239までは「待たなくてよい失敗を早く切る」方向、v2.1.288は「長い失敗を打ち切る」方向でした。v2.1.295はこの延長で、待つべき失敗でも待つ時間の天井を利用者が決められるようになります。
固定バージョンのランナーを使っているチームは、この表が更新の判断材料になります。v2.1.239より前のwatchdogは、支出上限の429でも無期限に再試行していました。使用クレジットが尽きたジョブが、解消しない状況をそのまま待ち続ける形です。v2.1.295より前では、429・529の待機そのものに天井を置く手段がなく、止める方法はCI側のtimeout-minutesか人の操作だけでした。
また、v2.1.281とv2.1.288の修正は、どちらも待機が必要以上に延びる側の不具合でした。無人で動かすジョブは、失敗が静かに長引くのが最も見つけにくいので、バージョンを上げたあともtimeout-minutesの外枠は残しておくと安全です。
よくあるつまずき
- 値を秒で書いてしまう:
1800と書くと1.8秒です。ミリ秒なので桁数を数える 30mや1.5のような書き方: 正の整数を数字だけで書く仕様です。それ以外を渡したときの扱いは記載がないため、確実に整数で指定する- 設定したのに待ち続ける: ジョブの起動より前に、環境変数が反映されているかを確認する。watchdog自体が
1になっていないと、この変数は意味を持たない - バージョンが古い: v2.1.295より前では変数が存在しない。固定したバージョンのランナーでは、更新してから設定する
- 設定した時刻ちょうどで止まると思い込む: 上限を使い切ったあとに次の
429か529が来て初めてリクエストが終わるので、実際の停止は設定値より遅れることがある - 支出上限の
429に期待してしまう: 支出上限や使用クレジット切れの429は、この変数と無関係にすぐ失敗する。待機を短くしても結果は変わらない
まとめ
CLAUDE_CODE_RETRY_WATCHDOGの「無期限に粘る」に、リクエスト単位の出口が付きました。1つのリクエストが障害に飲み込まれるのを防ぐ道具であり、ジョブ全体の長さはCIのtimeout-minutesで別に縛る分担になります。
数値は10分から30分を起点に置き、失敗時にジョブが何をするかから逆算して伸ばしていくと、決めやすくなります。リリースの全体像はv2.1.295のリリースノートにあります。