Claude Media
Anthropic SDKの既定リトライ・タイムアウトは言語で違う — hooksとMCPサーバーの盲点

Anthropic SDKの既定リトライ・タイムアウトは言語で違う — hooksとMCPサーバーの盲点

Claude Codeのhooksや自作MCPサーバーでAnthropic SDKを呼ぶ際、7言語のリトライ回数・タイムアウト秒・プロキシ環境変数対応の既定値の違いをまとめます。

hooksとMCPサーバーでAnthropic SDKを呼ぶと、時間の予算が食い違う

Claude CodeのhooksやMCPサーバーの中でAnthropic SDKを呼ぶと、Claude Code側が課す時間の予算とSDKの既定値がぶつかります。リトライ回数は7言語とも2回で共通ですが、タイムアウトの決まり方とプロキシ環境変数の扱いは言語ごとに違います。

SDKの既定値は、Claude Code本体の既定値とも別物です。

数字

SDKとClaude Code本体の既定値

  • SDKのリトライ

    2回

    Python・TypeScript・C#・Go・Java・PHP・Rubyで共通

  • SDKのタイムアウト

    10分

    言語によっては動的に変わる

  • 本体のリトライ

    10回

    CLAUDE_CODE_MAX_RETRIESの既定値

  • 本体のタイムアウト

    10分

    API_TIMEOUT_MSの既定値(600000ミリ秒)

本体の値は、Claude Codeが自分のAPIリクエストに使う設定です

本体側の環境変数はSDKの設定項目ではありません。hookの中で動くSDKのtimeoutとmax_retriesは、呼び出すコードの側で決めます。

この記事は、接続前に決まる設定値に絞ります。エラーが起きた後の例外クラスの言語別対応は、Claude APIのエラー形式とSDK例外クラスの言語別対応表にあります。

リトライは7言語とも2回で、タイムアウトも再送の対象になる

Client SDKのリトライとは、接続エラーや429・5xxが起きたときにSDKが自動で再送する仕組みです。Python・TypeScript・C#・Go・Java・PHP・Rubyのすべてで、既定は2回です。短い指数バックオフを挟みながら、接続エラー・408・409・429・5xx系のエラーで働きます。

設定用のオプション名は言語ごとに異なります。

言語オプション名
Pythonオプション名max_retries(クライアント初期化時、またはwith_options())
TypeScriptオプション名maxRetries
C#オプション名MaxRetriesプロパティ、またはWithOptions
Goオプション名option.WithMaxRetries()
Javaオプション名.maxRetries()ビルダーメソッド
PHPオプション名RequestOptions::with(maxRetries: ...)
Rubyオプション名max_retries

タイムアウトで失敗したリクエストも、この2回の再送対象です。Python・TypeScriptはタイムアウトの節に「タイムアウトしたリクエストは既定で2回リトライされる」と書いています。Ruby・PHPはリトライの節に「タイムアウトも既定でリトライされる」とあります。書かれている場所が違うだけで、挙動は同じです。

つまり、1回の呼び出しで待つ最長時間は「タイムアウト × 3回分」に再送の待ち時間を足した長さです。この掛け算が、後半のhooksの節で効いてきます。

タイムアウトは10分が基本で、動的に伸びるのはTypeScriptとJavaだけ

非ストリーミングのMessagesリクエストは、Python・C#・Java・Ruby・Goが10分を既定にしています。TypeScriptとJavaは、さらにmax_tokens(JavaはmaxTokens)の値で既定値が動きます。ただし、動く条件が逆です。

くらべる

動的タイムアウトの条件

ストリーミングしないときだけ

TypeScript

大きなmax_tokensを指定すると、60 * 60 * maxTokens / 128000秒まで伸びます。下限は10分、上限は60分です。

しても、しなくても

Java

ストリーミングでは同じ式で10分から60分まで伸びます。ストリーミングしない場合は、30秒から10分の範囲でmaxTokensに応じて変わります。

Javaの非ストリーミングは、他の言語の10分より短くなる側に動くため、hookの予算には有利に働きます。

長くかかりそうな非ストリーミングのリクエストを、SDKが先に拒否する言語もあります。PythonはValueErrorを投げ、TypeScript・Go・Javaもエラーを返します。この4言語では、ストリーミングにするかtimeoutを明示すればエラーは出ません。

長い非ストリーミングが嫌われるのは、アイドル状態の接続を切るネットワークがあるためです。PythonはTCPのkeep-aliveを設定して影響を和らげます。TypeScriptも、fetch実装が対応していれば同様に設定します。Javaはサーバーへ定期的にpingを送ります。プロキシやファイアウォールを挟む環境のhooksでは、この差が効きます。

Rubyには別の癖があります。接続プールの空きを待つ時間も、リクエストのタイムアウトに数えられます。並列でツールを呼ぶMCPサーバーでは、実際の通信より先に上限を使い切ることがあります。

PHPのSDKドキュメントには、タイムアウトの既定値や設定方法の節がありません。エラー分類の表に、TimeoutがAPITimeoutExceptionに対応するという記述があるだけです。C#は既定10分と、Timeoutオプションの設定例が載っています。

Goは非ストリーミングのMessages以外にタイムアウトの既定値がない

Goのドキュメントは「非ストリーミングのMessagesリクエストは既定で10分、それ以外のリクエストには既定タイムアウトがない」と書いています。Batch APIやFiles APIをcontext.WithTimeoutなしで呼ぶと、応答が返るまで待ち続けます。

Goにはもう一つ、他の言語と違う点があります。コンテキストのタイムアウトは、リトライしてもやり直されません。リトライ1回ごとの上限を付けるには、option.WithRequestTimeout()を使います。

// リトライを含めたリクエスト全体の上限
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
 
client.Messages.New(
    ctx,
    params,
    // 1回の試行ごとの上限
    option.WithRequestTimeout(20*time.Second),
)

全体の上限と1回ごとの上限を別々に持てるため、総時間が決まっているhookのような場面では扱いやすい設計です。

プロキシ環境変数の扱いは言語ごとに違う

企業ネットワーク配下でhooksやMCPサーバーを動かすとき、HTTP_PROXYとHTTPS_PROXYをSDKが読むかどうかは言語で分かれます。

Pythonはv0.54.0以降、http_client引数を渡さずにクライアントを作れば、環境変数のプロキシ設定を自動で拾います。仕組みと、自分でhttp_clientを渡すときの明示設定はAnthropicのPython SDKでプロキシ環境変数が反映されない問題を解決するにあります。

TypeScriptのドキュメントは、ランタイムごとのfetchOptionsで明示する方法を載せています。Node.jsはundici.ProxyAgent、Bunはproxyフィールド、DenoはDeno.createHttpClientです。Javaはjava.net.Proxyを渡す.proxy()ビルダーメソッドで設定します。TypeScriptとJavaは、ドキュメントが明示設定の方法を案内している言語です。C#・Go・PHP・RubyのSDKドキュメントには、プロキシの節がありません。

hookの予算にSDKの呼び出しを収める

hookには種類ごとに既定のタイムアウトがあります。command・http・mcp_tool型の既定は次のとおりです。

数字

hookの既定タイムアウト

  • 多くのイベント

    600秒

  • UserPromptSubmitなど

    30秒

    PreModelSwitch・PostModelSwitchも同じ

  • MessageDisplay

    10秒

  • SessionEnd

    1.5秒

    hook全体で共有する予算

command・http・mcp_tool型の場合

予算を超えたhookは打ち切られ、出力は捨てられます。UserPromptSubmitでは、追加のコンテキストが付かないままプロンプトがClaudeに届き、タイムアウトしたhookの名前がトランスクリプトに出ます。PreModelSwitchでは、タイムアウトがモデルの切り替え自体を止めます。

SessionEndの1.5秒は固定ではありません。設定ファイルでhookに長いtimeoutを指定すると、全体の予算が最大60秒まで引き上がります。環境変数CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MSでも指定できます。

SDKの呼び出しをhookに入れる前に、次の順で見積もります。

手順

hookからSDKを呼ぶ前の見積もり

  1. 1

    イベントの予算を調べる

    上の既定値か、hookの設定に書いたtimeoutが予算です。

  2. 2

    SDKの最悪時間を出す

    timeout × (1 + max_retries)に、再送の待ち時間を足します。max_retriesが既定の2でtimeoutが10分なら、最悪で30分を超えます。

  3. 3

    収まらなければどちらかを動かす

    hook側のtimeoutを引き上げるか、SDK側のtimeoutとmax_retriesを絞ります。

UserPromptSubmitの30秒に収める例です。設定ファイルでは、hook側のtimeoutを秒で書きます。

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/classify_prompt.py",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

hookの中のPythonでは、SDKの上限を予算より小さく絞ります。

from anthropic import Anthropic
 
client = Anthropic(
    timeout=8.0,      # 1回の試行の上限(秒)
    max_retries=1,    # 8秒 x 2回 = 最悪16秒 + バックオフ
)

TypeScriptでも考え方は同じです。

const client = new Anthropic({
  timeout: 8000,   // ミリ秒
  maxRetries: 1
});

async: trueを付けたcommand型のhookには、Claude Codeがタイムアウトを強制しません。ただし、バックグラウンドで動くため、SDKのリトライが長引いても気づきにくくなります。

自作MCPサーバーのリクエスト上限は60秒が下限になる

MCPサーバーのツール内でSDKを呼ぶ場合、Claude Code側の制約はhooksと別の仕組みです。ツール実行全体の上限はMCP_TOOL_TIMEOUTで、未設定なら約28時間です。サーバーごとに.mcp.jsonのtimeout(ミリ秒)で上書きでき、1000未満の値は無視されます。

HTTP・SSE・claude.aiコネクター経由のサーバーには、これとは別にリクエストごとのタイマーがあります。このタイマーは、サーバーが最初の応答バイトを返すまでの時間を測ります。値は、60秒、そのサーバーに適用されるツールのタイムアウト、MCP_TIMEOUTのうち最大のものです。未設定時の28時間は、この比較に入りません。stdioとWebSocketのサーバーには、このタイマーがありません。

無応答を検出する別の上限もあります。CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUTの既定は、ネットワーク経由のサーバーで5分、stdioサーバーで30分です。応答も進捗通知も届かない時間がこの長さに達すると、ツール呼び出しは中断されます。この変数にはClaude Code v2.1.187以降が必要で、0にすると無効になります。サーバー個別のtimeoutを設定すると、この下限が引き上げられます(v2.1.203以降)。

SDKの結果を待ってから初めて応答を返すHTTP型のツールは、最初のバイトまでの60秒に先に当たります。SDKのtimeout × (1 + max_retries)が60秒に収まるよう絞るか、.mcp.jsonでサーバーのtimeoutを引き上げます。後者の例です。

{
  "mcpServers": {
    "summarizer": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "timeout": 600000
    }
  }
}

MCP_TIMEOUTとMCP_TOOL_TIMEOUTの役割の違いはMCP_TIMEOUTとMCP_TOOL_TIMEOUTの違いで扱っています。長い生成をストリーミングで返す設計はClaude APIで長時間リクエストがタイムアウトする対策にあります。

Rustなど7言語にない言語でMCPサーバーを書く場合

AnthropicがClient SDKを提供するのは、Python・TypeScript・C#・Go・Java・PHP・Rubyの7言語です。MCPプロトコルの対応言語はこれより広く、Rust向けMCPサーバーSDKのような実装ガイドもあります。

Rustで書いたMCPサーバーのツールからAnthropic APIを呼ぶなら、リトライ回数・タイムアウト・プロキシ検出のどれも、SDKの既定値は使えません。/v1/messagesを生のHTTPクライアントで呼ぶか、コミュニティ製のクライアントを選びます。再送と上限時間は自分で書くことになります。MCPサーバー自体の作り方はMCPサーバー自作ガイドにあります。

言語別の既定値早見表

言語リトライ既定タイムアウト既定プロキシ環境変数
Pythonリトライ既定2回タイムアウト既定10分プロキシ環境変数自動検出(v0.54.0以降)
TypeScriptリトライ既定2回タイムアウト既定10分(非streamingかつ大きいmax_tokensで最大60分)プロキシ環境変数ドキュメントは明示設定を案内
Javaリトライ既定2回タイムアウト既定10分(streamingは最大60分、非streamingは30秒〜10分)プロキシ環境変数ドキュメントは明示設定を案内
C#リトライ既定2回タイムアウト既定10分プロキシ環境変数ドキュメントに記載なし
Goリトライ既定2回タイムアウト既定非streamingのMessagesのみ10分、他は既定なしプロキシ環境変数ドキュメントに記載なし
PHPリトライ既定2回タイムアウト既定ドキュメントに記載なしプロキシ環境変数ドキュメントに記載なし
Rubyリトライ既定2回タイムアウト既定10分プロキシ環境変数ドキュメントに記載なし

まとめ

どの言語でも、既定の10分とリトライ2回のままでは、最も長いhookの600秒にも収まりません。試行ごとの上限と全体の上限を別に持てるのはGoだけなので、それ以外の言語ではtimeoutとmax_retriesの掛け算で総時間を管理します。

参照した一次ソース

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

  1. platform.claude.com/docs/en/api/client-sdks
  2. platform.claude.com/docs/en/cli-sdks-libraries/sdks/python
  3. platform.claude.com/docs/en/cli-sdks-libraries/sdks/typescript
  4. platform.claude.com/docs/en/cli-sdks-libraries/sdks/java
  5. platform.claude.com/docs/en/cli-sdks-libraries/sdks/go
  6. platform.claude.com/docs/en/cli-sdks-libraries/sdks/csharp
  7. platform.claude.com/docs/en/cli-sdks-libraries/sdks/php
  8. platform.claude.com/docs/en/cli-sdks-libraries/sdks/ruby
  9. code.claude.com/docs/en/hooks
  10. code.claude.com/docs/en/env-vars
  11. code.claude.com/docs/en/mcp
この記事を共有:XはてブLinkedIn