Claude Media
retry exhaustion(リトライ枯渇)をClaude Codeで検知する方法

retry exhaustion(リトライ枯渇)をClaude Codeで検知する方法

Claude CodeのAPIリトライが尽きた瞬間をOTelイベントで検知する手順。api_errorのattempt属性と実効上限の突き合わせ、名前が紛らわしいapi_retries_exhaustedの実際の発火条件まで確認します。

Claudeのリトライ枯渇はどのイベントで検知するか

Claude Codeは失敗したAPIリクエストを内部で自動的にリトライします。リトライの途中経過はログイベントになりません(トレースを収集していれば、試行ごとのgen_ai.request.attempt span eventで追えます)。リトライを打ち切って失敗が確定したときだけclaude_code.api_errorイベントが1回発行され、attempt属性に合計の試行回数が入ります。

枯渇の検知は、このattemptを設定から導いた実効上限と突き合わせる作業です。「attemptが大きい行を拾う」だけでは、上限が10回の環境と300回の環境が混ざったときに誤検知と見逃しの両方が出ます。

claude_code.api_retries_exhaustedという名前のイベントもありますが、文字どおりの「上限到達」では発火しません。名前だけで判断するとアラート設計を誤ります。

前提条件

項目必要な状態
テレメトリの有効化必要な状態CLAUDE_CODE_ENABLE_TELEMETRY=1とイベント用のOTEL_LOGS_EXPORTER設定が済んでいること(設定手順はClaude CodeのOpenTelemetryで利用量とコストを可視化)
バックエンド必要な状態OTLPログを受信できる収集基盤
Claude Codeのバージョン必要な状態CLAUDE_CODE_RETRY_WATCHDOG変数自体はv2.1.186以降で利用可能(既定300回化とキャップ解除はv2.1.199以降)、client_request_id属性はv2.1.214以降が必要

OTEL_METRICS_EXPORTERだけを設定してOTEL_LOGS_EXPORTERをnoneのままにすると、この2つは届きません。api_errorもapi_retries_exhaustedもイベント側の信号で、メトリクスとは別のエクスポーター設定だからです。

attemptの上限は設定でどう変わるか

CLAUDE_CODE_MAX_RETRIESはリトライ回数を上書きする環境変数です。枯渇した行のattemptは「実効上限+1」になり、設定ごとの目安は次のとおりです。

数字

枯渇時の attempt の目安(一時的なエラーの場合)

  • 既定のまま

    11

    MAX_RETRIES 未設定(上限10回)

  • MAX_RETRIES を設定

    最大16

    v2.1.186以降は15回でキャップ

  • WATCHDOG=1

    301以上

    既定300回。429と529は無期限

一時的なエラーはサーバーエラー・タイムアウト・接続切断など

CLAUDE_CODE_RETRY_WATCHDOGは評価ハーネス・CIジョブ・リモートワーカーなど無人セッション向けの設定です。v2.1.199以降はバックオフが約3時間分に延び、間隔は最大5分です。レスポンスにレート制限のリセット時刻があれば、その時刻まで待ちます。CLAUDE_CODE_MAX_RETRIESを明示していても、15回のキャップは外れます。

例外が1つあります。標準速度のリクエストで返る、支出上限や利用クレジット枯渇を示す429は、watchdogが有効でも即座に失敗します。ゲートウェイの支出上限のように一定周期でリセットされるものも、即座に失敗します。ファストモードのリクエストは別の扱いです。v2.1.239より前のwatchdogは標準速度の支出上限もリトライし続けていたため、古いバージョンが混ざる環境では挙動が分かれます。

attemptが小さい失敗は原因ごとに読み分ける

attemptが上限より小さい行は「軽微な失敗」とは限りません。リトライの扱いは失敗の種類ごとに違い、バジェットを使い切る前に打ち切られるものが複数あります。

失敗の種類リトライの扱い
一時的なサーバーエラー・過負荷・タイムアウトリトライの扱い実効上限まで
Claudeが思考を終えたあと、テキストやツール呼び出しの前に届いたサーバーエラーリトライの扱い最大2回(v2.1.284より前はそこでターンを終了)
同じ地点での接続切断リトライの扱い短い間隔で最大2回
ストリームの停止リトライの扱い10回の枠の外で最大1回
AWS・Google Cloudの認証情報の読み込み失敗リトライの扱い最大2回
TLS証明書の検証失敗リトライの扱いリトライなし(最初の試行で報告)
支出上限の429(標準速度のリクエスト)リトライの扱いwatchdogが有効でも即失敗

Claudeがテキストやツール呼び出しを書き終えたあとに届いたサーバーエラー、切断、ストリームの停止は、同じリクエストを再実行しません。同じツール呼び出しが二重に実行されかねないためで、そこまでの出力は保持されます。出力コンテンツフィルターにブロックされた応答は、v2.1.285から再送されず、フィルターのエラーがすぐ表示されます(それより前は数分間リトライし続けることがありました)。組織のポリシーで拒否されたリクエストも再送されません(v2.1.239より前は再送し得ました)。

attemptが1〜2の行には、証明書エラーや支出上限の429(いずれもリトライしない)、認証情報の失敗(最大2回)のように、性質の違う原因が混ざります。ダッシュボードではstatus_codeとerrorを並べて切り分けます。status_codeは429のようなHTTPエラーなら数値で入りますが、接続障害では入りません。

api_errorのattempt属性で枯渇を判定する

アラートクエリの基本形は、claude_code.api_errorをattemptの閾値でフィルタし、session.id・user.id・modelなどで集計するものです。固定値の11をハードコードすると、watchdog有効な環境ではattemptが11〜300のまま400や支出上限の429などで終わった行まで、枯渇(301以上)と数えてしまいます。

閾値を設定に合わせて可変にできないなら、CLAUDE_CODE_RETRY_WATCHDOGを使うグループとそうでないグループでダッシュボードを分けるのが確実です。クエリは概ね次の形になります。

※疑似コード
filter event.name == "api_error"
  AND attempt >= threshold(user.group)
group by session.id, model, query_source

query_sourceを集計に加えると、メインセッションのrepl_main_threadから出た枯渇と、サブエージェントやコンパクト処理(compact)から出たものを分けて見られます。サブエージェントを大量に並列実行するワークフローでは、メインは正常でもサブエージェント側だけが枯渇することがあり、この区別が無いと問題の所在を見誤ります。

api_retries_exhaustedは名前ほど厳密ではない

claude_code.api_retries_exhaustedはapi_errorと同時に発行される専用イベントです。total_attempts(合計試行回数)とtotal_retry_duration_ms(全試行を通した経過時間)を直接持つので、閾値の計算なしで使えます。

ただし定義は「1回を超える試行のあとにAPIリクエストが失敗したときに1回だけ記録される」です。設定上の上限まで使い切ったことは条件に入っていません。定義だけを読むと、2回目以降の試行で400のような非リトライ対象エラーに転じて打ち切られた場合も、試行回数が1を超えるので条件を満たします。

くらべる

2つの信号の使い分け

量と遅延を測る

api_retries_exhausted

失敗がどれだけ起きているかの量と、リトライが利用者に与えた遅延を測る用途です。

真の枯渇を拾う

api_error の attempt

attemptを実効上限と突き合わせれば、上限まで使い切った行だけをアラート対象にできます。閾値の管理が必要な代わりに、途中で非リトライ対象エラーに転じた行は混ざりません。

枯渇のあとセッションが回復したかを見分ける

api_errorが出たセッションが使い続けられたのか、そこで止まったのかは、イベントの前後関係で判定します。

手順

回復したセッションと止まったセッションの見分け方

  1. 1

    session.id でグループ化する

    api_errorが出たセッションのイベントを、session.idでまとめます。

  2. 2

    エラーより後の api_request を探す

    api_errorのタイムスタンプより後にapi_requestイベントがあるかを確認します。

  3. 3

    有無で分ける

    あればエラーを越えて継続したセッションです。無ければ、その時点で止まった可能性が高いと見ます。

画面上では、リトライ中のスピナーにRetrying in Ns · attempt x/yのカウントダウンが出ます。開発者から「画面にこの表示が出ていた」と報告があったときは、同じ時間帯のattemptを探す手がかりになります。ネットワーク断・TLSハンドシェイク失敗・レート制限は、1回目から具体的な理由が表示されます。それ以外のエラーは最初はAPI errorと表示され、v2.1.198以降は3回目の試行で具体的な理由に切り替わります。CLAUDE_CODE_MAX_RETRIESが3回未満なら最後の試行で切り替わり、それより前のバージョンは最後の試行でしか切り替わりません。

個々の利用者に枯渇を紐づける場合は、認証方式に注意が要ります。直接のAPIキーで動くセッションには、Claudeアカウントの識別子(user.emailなど)が乗りません。Bedrock・Google Cloud's Agent Platform・Microsoft Foundry経由でも同じです。

残るのはuser.idとsession.idだけなので、OTEL_RESOURCE_ATTRIBUTESでenduser.idを埋め込みます。managed settingsファイルで統制するか起動ラッパー経由で、利用者ごとに設定します。Claude apps gatewayに/loginでサインインしたセッションは例外で、IdPの識別子がCLIによって自動で付きます。

試行回数の数え方はバージョンで変わっている

リトライ周りは直近のリリースで続けて手が入っています。閾値を固定する前に、手元の環境が混在していないかを見ます。

  • v2.1.284: 応答の途中で接続が切れた場合のリトライが、リクエスト全体のバジェットを共有するようになった。失敗した呼び出しは、その分早く諦める
  • v2.1.285: ストリーミングが失敗し続けると最大21回までリトライされる問題を修正。非ストリーミングのフォールバックが、新しい枠でなくリクエストのバジェットを共有する
  • v2.1.286: 1回のモデル呼び出し全体を1つの上限で数える。既定の設定では、失敗する呼び出しが送るリクエストは最大14回

監視ドキュメントは既定の枯渇時のattemptを11としていて、v2.1.286の変更履歴は最大14リクエストと書いています。2つの数字は一致しません。閾値は、自環境のapi_errorでattemptの最大値を実測して決めるのが確実です。

なお、最初のバイトの期限が走る接続で、レスポンスヘッダーが返らないストリーミングリクエストは、リトライバジェットの範囲内でモデルリクエストごとに最大1回だけ再送されます。CLAUDE_CODE_RETRY_WATCHDOGを設定すると、この1回の上限は適用されません。

v2.1.285の変更履歴には、タイムアウトした非ストリーミングのフォールバックの再送回数を抑えるCLAUDE_CODE_NONSTREAMING_TIMEOUT_RETRIESが載っています。環境変数の一覧には出てこず、変更履歴にしか登場しません。

よくあるつまずき

  • watchdogを入れたチームだけ誤検知が増える: attempt >= 11の固定値が、無人セッションで枯渇以外の理由で終わった行まで枯渇扱いにするためです。設定変更の時点でアラートルールも見直します。支出上限の設計そのものはコスト管理の運用ルールに従います
  • 400が枯渇に混ざる: api_retries_exhaustedだけで集計すると、定義上は途中で非リトライ対象エラーに転じた行まで入る可能性があります。errorとstatus_codeの中身で除外します
  • 認証情報の失敗が小さいattemptで並ぶ: AWSやGoogle Cloudの認証情報の読み込み失敗は最大2回のリトライで打ち切られます。原因は開発者側の認証で、サービス障害とは別の対処になります
  • ログ設定がバックエンドに反映されていない: 利用者ごとにOTEL_LOGS_EXPORTERを配ると漏れが出ます。managed settingsに集約すると、開発者ごとの設定漏れを防げます

よくある質問

gateway経由のセッションでも同じ検知方法が使えますか

使えますが、送信経路が変わり、設定も1つ要ります。/loginでClaude apps gatewayにサインインしたセッションのOTLP/HTTPエクスポートは、まずゲートウェイに届きます。ゲートウェイはtelemetry.forward_toの宛先へそのまま中継します。

宛先はシグナルごとに有効化する方式で、既定はメトリクスだけです。api_errorはログイベントなので、logs: trueを指定しないと届きません。テレメトリを有効にするにはpublic_urlも必須です。

telemetry:
  forward_to:
    - url: https://otel-collector.internal.example.com
      headers:
        Authorization: ${OTLP_TOKEN}
      # Per-signal opt-in. Default: metrics only.
      metrics: true
      logs: true

ログにはコマンドやファイルパスが載るので、転送先はアクセス制御のあるコレクターに限ります。ゲートウェイを経由させず直接送らせるには、managed policyのenvブロックにOTEL_EXPORTER_OTLP_ENDPOINTを置きます。値はコレクターのhttps://ベースURLです。この値を配ると、各開発者のCLIにセキュリティの承認ダイアログが表示され、承認されるまで反映されません。各開発者のマシンでv2.1.265以降が必要で、それより前のクライアントは中継経由で送ります。

client_request_idはattempt以外に何の役に立ちますか

client_request_idはクライアント側で生成され、x-client-request-idヘッダーとして送られるUUIDです。サーバー側のrequest_idが取れないタイムアウトや接続エラーでも残るので、リクエストとレスポンスを突き合わせるキーになります。ファーストパーティAPI接続でだけ付き、サードパーティプロバイダー経由や非ストリーミングのフォールバック経由では付きません。

まとめ

アラートの条件にはapi_errorのattemptを使い、api_retries_exhaustedは量と遅延の把握に回す使い分けが安全です。閾値を見直すタイミングは、watchdogを導入するときと、v2.1.284〜286が混在している間です。

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