Claude Media
「API returned an empty or malformed response」の原因と対処

「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と書かれているのに失敗扱いになるので、最初は戸惑います。出る条件は次の連鎖です。

  1. Claude Codeがストリーミングでリクエストを送る
  2. ストリームが途中で失敗する
  3. Claude Codeが同じリクエストを非ストリーミングで送り直す(非ストリーミング再試行)
  4. 送り直しは成功ステータスで返るが、本文が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応答です。

切り分けの順序

やることを迷ったときの並べ方を、筆者の整理として示します。

  1. ANTHROPIC_BASE_URL が設定されているかを確認する。ゲートウェイを使わない構成でこのエラーが出るなら、ネットワークのサインインページかプロキシを疑う
  2. Response: 句でHTML、リクエストid、サーバー名を確認する
  3. ゲートウェイがあるなら、上のcurlで直接叩く
  4. ゲートウェイの非ストリーミング経路だけが壊れているなら、フォールバックを無効にする
  5. 経路の接続そのものが怪しいならClaude Codeプロキシ設定やUnable to connect to APIの原因と対処の切り分けへ進む

ゲートウェイ側の設定や検証手順はClaude CodeをLLMゲートウェイに接続する方法にまとめています。

直す場所は手元とは限らない

公式の説明では、原因の筆頭は経路上のプロキシ、ゲートウェイ、ネットワークのサインインページです。Claude Codeが「これはAPIの応答ではない」と判定した結果がこのエラーなので、再インストールでは経路の問題は直りません。まず Response: 句を読み、返事をしたのがどの機器かを特定することが、最短の解決経路になります。

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