「API returned an empty or malformed response」の原因と対処
Claude Codeに出る「API returned an empty or malformed response (HTTP 200)」の原因は、多くの場合プロキシやゲートウェイの横取りです。読み方と切り分け手順をまとめます。
API returned an empty or malformed response (HTTP 200) は、Claude Codeが「成功のはずの応答」を受け取ったのに、中身がClaude APIのメッセージではなかったときに出るエラーです。ほとんどの場合、Claude CodeとAPIの間にいるプロキシ、LLMゲートウェイ、ネットワークのサインインページが、APIの代わりに返事をしています。
このエラーが出る条件
エラー文にHTTP 200と書かれているのに失敗扱いになるので、最初は戸惑います。出る条件は次の連鎖です。
- Claude Codeがストリーミングでリクエストを送る
- ストリームが途中で失敗する
- Claude Codeが同じリクエストを非ストリーミングで送り直す(非ストリーミング再試行)
- 送り直しは成功ステータスで返るが、本文がClaude APIのメッセージではない
4の「本文」の例として、HTMLのエラーページやサインインページ、空の本文、別形式のJSONが挙げられています。ここで再試行はもう行われず、そのターンはこのエラーで終わります。
つまりこのエラーは、ストリーミングの失敗と、非ストリーミング経路の異常が重なった結果です。ストリーミングの失敗だけなら、通常はリトライで吸収されます。
なぜ自動で直らないのか
Claude Codeは一時的な失敗を、指数バックオフをはさんで最大10回まで再試行します。エラー画面が出た時点で、その失敗に当てはまる再試行はもう済んでいる、というのが公式の前提です。
このエラーは、その再試行をしない失敗の一覧に入っています。同じ一覧には、TLS証明書の検証失敗、Bedrockのストリームでコンテンツタイプが想定と違う場合、組織のポリシーチェックに拒否されたリクエストが並びます。Bedrockの項には「ゲートウェイやプロキシが応答を書き換えるなら、再送も同じように書き換えるから」という理由が添えられています。
本記事のエラーの理由は明記されていませんが、並びを見ると共通点は「再送しても結果が変わりにくい失敗」です。これは筆者の読みです。プロキシがHTMLを返す環境なら、もう一度送っても同じHTMLが返ってくるでしょう。待っても直らないので、経路の側を見にいくことになります。
エラー文の後ろを読む
冒頭の一文のあとに、何が返ってきたかを示す情報が続きます。原因の切り分けは、ほぼここで決まります。
Response:句 — コンテンツタイプ、本文の種類(body is an HTML page、empty bodyなど)、サイズ(バイト)、Anthropicのリクエストidの有無。nginxやcloudflareのようなサーバー名、cf-rayやviaのような中継系ヘッダーがあれば、それも並ぶ- 失敗したストリーミングリクエストのidと、再試行のきっかけになった失敗の内容。ストリームが開いていた場合は、届いたイベント数と、失敗時点での無音時間も出る
Anthropic APIが返した応答なら、リクエストidが付くのが普通です。idが無く、HTMLで、サーバー名が見える。この3点がそろえば、間にいる別のシステムが返したと見てよいでしょう。
Response: 句の手がかり | 疑う相手 |
|---|---|
body is an HTML page でリクエストidなし | 疑う相手プロキシ、ゲートウェイ、サインインページ |
nginx cloudflare などのサーバー名、cf-ray via ヘッダー | 疑う相手経路上のリバースプロキシやCDN |
empty body | 疑う相手本文を捨てるゲートウェイ、途中で切るプロキシ |
| JSONだがClaude APIの形式ではない | 疑う相手別形式で返すゲートウェイ |
この表の対応は、公式が挙げた本文の種類とサーバー名・ヘッダーの手がかりを組み合わせた筆者の目安です。公式に「この手がかりならこの機器」という対応表があるわけではありません。
切り分けと対処
経路を直接叩いて確かめる
ゲートウェイ経由なら、Claude Codeを介さずに同じ経路へ直接リクエストを送ります。公式のゲートウェイ接続ガイドにある1トークンのリクエストです。
curl -sS -w '\n%{http_code}\n' -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'{"id":"msg_ で始まり "content":[...] を含むJSONが返れば、ゲートウェイには届いていて認証も通っています。HTMLが返るなら、そのルートが非APIの応答を返している証拠です。キーを x-api-key ヘッダーで受けるゲートウェイなら、ヘッダーを置き換えます。
コマンド中の claude-sonnet-4-6 は公式の例に書かれたモデル名です。ゲートウェイが別のモデル名で公開しているなら差し替えます。ただし、モデル名が未知だというエラーが返っても、URLと認証は通っています。ゲートウェイは認証を済ませてからモデル名を弾くため、公式も、このテストのためにゲートウェイが扱うモデルを探す必要はないとしています。
返ってきた内容ごとの読み方は次のとおりです。
| curlの結果 | 分かること |
|---|---|
{"id":"msg_ で始まり "content":[...] を含むJSON | 分かることゲートウェイに届き、認証も通っている |
| 未知のモデルを示すエラー | 分かることURLと認証は有効。モデル名だけが合っていない |
401 | 分かること認証情報が拒否された。変数の選び違いを疑い、もう一方の変数に替える |
| HTMLの本文 | 分かることそのルートが非APIの応答を返している |
| すぐに失敗する(接続拒否、ホスト名を解決できない) | 分かること本記事のエラー以前に、アドレスかネットワーク経路の問題 |
401 になる理由は、ANTHROPIC_AUTH_TOKEN が Authorization: Bearer に、ANTHROPIC_API_KEY が x-api-key に入るという違いにあります。認証情報を別の変数に入れると、ゲートウェイが読まないヘッダーで届いてしまいます。
curlが通れば安心、とも言い切れません。ゲートウェイの前段にあるWAF(Webアプリケーションファイアウォール)が、Claude Codeのプロンプトに含まれるXML風のタグやソースコードをクロスサイトスクリプティングの検査ルールに引っかけ、短いcurlは通るのに実際のセッションだけ弾く事例が、ゲートウェイの案内に載っています。この事例で返るのは 403 とHTMLの本文で、本記事の HTTP 200 とは状況が違います。ただ、「curlの1トークンが通ること」と「実際の会話が通ること」は別だと知っておくと、切り分けで迷いません。
curl に -i を足すと応答ヘッダーも見えるので、Response: 句のコンテンツタイプやサーバー名と突き合わせやすくなります(これは公式のコマンドへの筆者の追加です)。
原因別のやること
- ゲートウェイやプロキシが返している場合 — その経路が非APIの応答を返す理由を直す。ゲートウェイのトラブルシューティング表でも、原因は「ゲートウェイか中間プロキシが非APIの応答を返した、多くはHTMLのエラーページかログインページ」とされている
- ゲストWi-Fiなど、サインインページのあるネットワーク — ブラウザーでサインインを済ませてから再試行する
- 非ストリーミング経路だけがゲートウェイで壊れている場合 — 環境変数
CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK=1を設定する
最後の環境変数は、ストリーミング中に失敗したリクエストを、非ストリーミングへの切り替えでなく通常のリトライ経路に流します。
export CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK=1
claude例外が1つあります。ストリーミングのエンドポイント自体が 404 を返すときは、この変数を設定していても非ストリーミングにフォールバックします。
この変数はエラーを消す魔法ではなく、フォールバックという1つの経路を閉じるだけです。環境変数の説明には、プロキシやゲートウェイが原因でフォールバックがツールの二重実行を生むケースにも使えると書かれています。ゲートウェイの問題そのものは直らないので、ストリーミング側が正常に通るかは別に確かめる必要があります。
二重実行が気になる理由も、公式の別の記述から読み取れます。ストリームが応答の途中で切れたとき、Claude Codeは完了したテキストやツール呼び出しのあとであれば、リクエストを再実行せず、完了済みの内容を残してツール呼び出しの結果から続きます。同じツール呼び出しを2回実行しかねないからです。プロキシやゲートウェイが原因でフォールバックが同じ状況を作ると、環境変数の説明にある「ツールの二重実行」が起きます。ファイルの編集やコマンドの実行が重複したように見えるときは、このフォールバックを疑う手があります。症状の見え方は公式には書かれておらず、上の理屈からの筆者の推測です。
似たエラーとの違い
同じ「ストリーミングと非ストリーミング」の文脈に、別のエラーが3つあります。混同しやすいので並べます。
| エラー | 起きること | 再試行 |
|---|---|---|
| 本記事のエラー | 起きること非ストリーミングの送り直しがHTTP成功でも、本文がAPIメッセージでない | 再試行しない |
Streaming response ended before any complete data was received | 起きることストリームが使えるデータを1つも返さず終わり、非ストリーミングで送り直した(その警告) | 再試行非ストリーミングで送り直す |
Bedrock streaming response has an unexpected content-type | 起きることAmazon Bedrockのストリームが期待するコンテンツタイプで来ない | 再試行しない |
No response from API | 起きること最初のバイトが期限内に来ない | 再試行最大1回 |
Streaming response ended before any complete data was received は、対処の入口が本記事とつながっています。この警告が出た直後に送り直しが成功すれば、そこで終わります。送り直しが「成功ステータスだが本文がAPIメッセージでない」なら、本記事のエラーに進みます。
No response from API は、応答ヘッダーが期限内に来ない場合の話で、本記事とは失敗の位置が違います。待ち時間の調整はClaude Codeの応答が止まったときのタイムアウト調整方法、同名エラーの詳細は「No Response From API」が繰り返し出る原因と対処にあります。
バージョンで変わった点
エラー文とその扱いは、v2.1.234とv2.1.271の2回のリリースで変わっています。
- v2.1.234より前 — メッセージは
intercepting the requestで終わり、Response:句も失敗リクエストの情報もなかった - v2.1.271より前 — 有効なAPIメッセージを
text/plainのような非JSONのコンテンツタイプで返す応答も、このエラーで終わっていた。LLMゲートウェイの中には、非ストリーミングの応答にそのコンテンツタイプを使うものがある
つまり、claude update で新しいバージョンに上げたあとに消えるエラーがあります。ゲートウェイが text/plain で正しいメッセージを返している場合です。古いバージョンで、エラー文に手がかりが少ないときは、まず更新が近道になります。更新後も出るなら、本物の非API応答です。
切り分けの順序
やることを迷ったときの並べ方を、筆者の整理として示します。
ANTHROPIC_BASE_URLが設定されているかを確認する。ゲートウェイを使わない構成でこのエラーが出るなら、ネットワークのサインインページかプロキシを疑うResponse:句でHTML、リクエストid、サーバー名を確認する- ゲートウェイがあるなら、上のcurlで直接叩く
- ゲートウェイの非ストリーミング経路だけが壊れているなら、フォールバックを無効にする
- 経路の接続そのものが怪しいならClaude Codeプロキシ設定やUnable to connect to APIの原因と対処の切り分けへ進む
ゲートウェイ側の設定や検証手順はClaude CodeをLLMゲートウェイに接続する方法にまとめています。
直す場所は手元とは限らない
公式の説明では、原因の筆頭は経路上のプロキシ、ゲートウェイ、ネットワークのサインインページです。Claude Codeが「これはAPIの応答ではない」と判定した結果がこのエラーなので、再インストールでは経路の問題は直りません。まず Response: 句を読み、返事をしたのがどの機器かを特定することが、最短の解決経路になります。