Opus 4.6のjson_schemaで空コンテンツを返す不具合と回避策
Opus 4.6でoutput_config.format(json_schema)を使うと空のcontentが返ることがあります。GitHub issueの経緯と回避策、公式ドキュメントが定義する正規の異常応答との違いをまとめます。
AnthropicのTypeScript SDKでoutput_config.formatにjson_schemaを指定してClaude Opus 4.6を呼び出すと、content配列が空のまま応答が返ることがあります。400エラーは出ず、stop_reasonも正常終了を示すend_turnのままなので、パース処理だけが静かに失敗する原因が分かりにくいバグです。GitHub issue #913の経緯と、Anthropicが認めた原因、8月末以降に再び挙がっている報告、実務での対処をまとめます。
Opus 4.6のjson_schema空コンテンツ問題とは
この不具合は、output_config.formatにtype: "json_schema"を指定したリクエストで、Claude Opus 4.6がcontentが空の応答を返す現象です。TypeScript SDKのGitHub issue #913で2026年2月17日に報告され、「ほぼ毎回再現し、まれに正常な応答が返る」という頻度で発生していました。
structured outputsとは、Claudeの応答を指定したJSON Schemaへ強制的に一致させるAnthropic API公式機能です。output_config.format(JSON outputs)とstrict: true(Strict tool use)という2つの仕組みの総称で、今回の不具合はこのうちJSON outputsの経路で起きています。報告時の環境はモデルclaude-opus-4-6、エンドポイントはMessages APIで、output_configを外す、モデルをOpus 4.5に切り替える、thinkingモードを有効にするのいずれかを行うと空応答は起きなくなっていました。
実際に何が返ってくるか
空応答時のレスポンスは200ステータスで返り、エラーメッセージは一切含まれません。contentが空配列になっている点だけが手がかりです。
{
"model": "claude-opus-4-6",
"type": "message",
"role": "assistant",
"content": [],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 1616,
"output_tokens": 6
}
}stop_reasonがend_turnということは、APIから見ればこの応答は正常に完了したターンです。output_tokensはわずか6で、スキーマに沿ったJSONを組み立てる前にモデルが応答を打ち切っていたことがうかがえます。呼び出し側のコードがcontent[0].textをそのままJSON.parseするような実装だと、空配列への添字アクセスでエラーになるか、undefinedを渡してパースが失敗します。
原因はモデル/API側にあった
Anthropicのエンジニアであるdtmeadows-antは、2026年8月5日のコメントでこの不具合の原因を明らかにしました。SDKのバグではなく、モデル/API側に原因があったと説明しています。
当時のOpus 4.6には、output_configでJSON Schemaを指定した際に、テキストを一切生成しないままターンを終えてしまう挙動があったといいます。dtmeadows-antはこの挙動をモデル/API側で修正したとし、issue報告者の最小リクエストを同日に再実行したところ、毎回スキーマ通りのcontentが返るようになったと述べています。同じコメントでは、Sonnet 4.6でも同様の修正が入ったと補足されています。
structured outputsの対応モデル一覧を確認すると、claude-opus-4-6は今もJSON outputsのGA対応モデルとして掲載されています。Sonnet 4.6・Sonnet 5も同様に名を連ねています。ベータ機能や実験的な組み合わせで起きた不具合ではなく、公式にGAとして案内されている経路の中で発生していた点が、この不具合を見落としにくくしていたと考えられます。
issueの経緯をタイムラインで追う
報告から修正表明、そして再発報告までの流れは次のとおりです。
| 日付 | できごと |
|---|---|
| 2026-02-17 | できごとissue #913が報告される。モデルはOpus 4.6、ほぼ毎回contentが空になる |
| 2026-05-05 | できごと別ユーザーがOpusとSonnet 4.6の両方で同様の症状を報告 |
| 2026-08-05 | できごとAnthropicのdtmeadows-antがモデル/API側の問題だったと説明し、修正済みと回答 |
| 2026-08-31 | できごと別ユーザーがSonnet 5で、thinkingのログが出た直後にcontentが空になる類似の症状を報告 |
| 2026-09-03 | できごと別ユーザーがSonnet 4.6とOpus 4.6の両方で同様の症状を報告 |
issueには4件のコメントが付いており、最新は2026年9月3日のものです。issue自体のステータスはclosedのままで、この最後のコメント以降、Anthropic側からの新しい返信は確認できません。
8月末以降の再発報告をどう読むか
8月5日の修正表明のあとに、症状が似た報告が2件続けて挙がっています。ただし対象モデルは元の報告(Opus 4.6)とは異なり、Sonnet 5やSonnet 4.6も含まれています。
同じ「contentが空」という見た目の症状でも、原因が2月の報告と同一かどうかは、公開されているコメントだけでは判断できません。モデルが増えている点を踏まえると、json_schema指定時にcontentが空で返る経路そのものが根深く、個別モデルごとの修正だけでは塞ぎきれていない可能性はあります。一方で、報告者が挙げている状況(thinkingのログ直後に空になる、など)は元issueの最小repro payloadとは条件が異なっており、別の引き金による類似症状という見方も成り立ちます。closedのまま追加コメントに反応がない状態は、公式が「解決済み」と判断しているのか、単に見落とされているだけなのかを外部から見分ける材料がありません。
公式ドキュメントが定義する「スキーマに一致しない」正規のケースとの違い
Anthropic公式のstructured outputsドキュメントは、出力がスキーマに一致しない正規のケースを3つ定義しています。issue #913で報告された空のcontent配列は、このいずれにも当てはまりません。
| 正規のケース | stop_reason | 特徴 |
|---|---|---|
| 拒否(refusal) | stop_reasonrefusal | 特徴安全上の理由でスキーマを無視した拒否メッセージが返る。200ステータスで課金は発生する |
| トークン上限到達 | stop_reasonmax_tokens | 特徴出力が途中で打ち切られ、不完全なJSONになる |
| enum値の大文字小文字ゆれ | stop_reason通常どおり(end_turn等) | 特徴値自体はenumの範囲内だが、先頭文字の大文字小文字だけ異なる |
| issue #913の症状 | stop_reasonend_turn(正常終了扱い) | 特徴contentが空、output_tokensも数個程度でJSONの組み立てが始まっていない |
拒否とトークン上限は、いずれもstop_reasonが専用の値になるため、レスポンスを見れば理由を切り分けられます。enum値のゆれはcontent自体は生成されており、比較の際に大文字と小文字の違いを無視して扱えば実害を避けられます。issue #913の症状はこのどれにも属さず、end_turnという「正常終了」のラベルのままcontentだけが空になる点が、この不具合を厄介にしていました。
回避策と実務での対処
issueのコメント欄には、いずれも空応答を解消したという3つの回避策が挙がっています。
| 回避策 | 内容 | 備考 |
|---|---|---|
| モデルをOpus 4.5に切り替える | 内容claude-opus-4-6をclaude-opus-4-5に変更する | 備考同じリクエストで空応答が発生しなくなったという報告 |
| thinkingモードを有効にする | 内容Opus 4.6のままextended thinkingを有効化する | 備考出力前に思考ブロックが挟まることで空応答を避けられたという報告 |
| output_configを外す | 内容output_config.format自体をリクエストから削除する | 備考structured outputsを使わない代わりに空応答も起きなくなる。スキーマ強制は失われる |
これらはissue報告者が個別のリクエストで確認した回避策であり、Anthropicが恒久対策として案内しているものではありません。8月5日の修正以降は本来不要になっているはずの回避策ですが、8月末以降の再発報告を踏まえると、選択肢として持っておく価値はあります。
3つの回避策に共通するのは、いずれもoutput_configが組み込む制約付きデコーディングの経路を迂回している点です。thinkingモードを挟むと、モデルは最終出力の前に思考ブロックを生成してからテキストを組み立てるため、スキーマ制約のもとでテキストを出さずにターンを終える不具合を通過しにくくなります。モデルをOpus 4.5へ切り替える回避策は、不具合の原因がOpus 4.6という特定バージョンのモデル側にあったことの裏返しです。output_configを外す回避策は、制約付きデコーディングの仕組み自体を使わなくなるため、スキーマ強制という機能の便益を失う代わりに空応答のリスクも消えます。
コード側での防御としては、content配列をパースする前にstop_reasonとcontent.lengthを確認する処理を挟むのが実務的です。stop_reasonがrefusalやmax_tokensであれば、それぞれ拒否・トークン不足として扱います。stop_reasonがend_turnなのにcontentが空、またはoutput_tokensが極端に少ない場合は、正規パターンのどれにも該当しない異常応答として扱います。リトライまたは上記の回避策を試す設計にしておけば、issueが再びcloseされたまま放置されても実装側で吸収できます。
const res = await anthropic.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: userPrompt }],
output_config: { format: { type: "json_schema", schema } },
});
if (res.stop_reason === "refusal") {
// 安全上の理由による拒否。スキーマには従わない
throw new SchemaRefusalError(res);
}
if (res.stop_reason === "max_tokens") {
// 途中で打ち切られた不完全な出力。max_tokensを上げて再試行
throw new IncompleteOutputError(res);
}
if (res.content.length === 0) {
// end_turnなのにcontentが空。issue #913と同型の異常応答
throw new EmptyContentAnomalyError(res);
}公式ドキュメントは、有効なスキーマで問題が続く場合はサポートへの問い合わせを案内しています。3つのstop_reason分岐とcontent.lengthのチェックを先に通しておけば、どの原因で失敗したかがログから追え、issue #913のような未修正のモデル側異常だけをリトライ対象として切り分けられます。
まとめ
Opus 4.6のjson_schema空コンテンツ問題は、2026年2月にGitHub issue #913で報告され、8月5日にAnthropicがモデル/API側の原因と修正を認めています。ただしその後の8月31日と9月3日に、Sonnet 5・Sonnet 4.6を含む複数モデルで似た症状が報告されています。issueがcloseされている状態だけを見て「解決済み」と決めつけるのは早計です。公式ドキュメントが定義する拒否・トークン上限・enum値ゆれのいずれにも当てはまらないend_turnラベル付きの空応答が起きた場合は、モデル切り替えやthinking有効化といった回避策を選択肢に持ちます。stop_reasonとcontentの長さを確認するチェックをコード側に残しておくと安全です。
output_config.formatの基本的な実装手順はClaude JSONモードの使い方にまとめています。structured outputsが受け付けるJSON Schemaの制約と、拒否・トークン上限・enum値ゆれの詳細はStructured outputsのJSON Schema制限をSDKが自動変換する仕組みで扱っています。TypeScript SDK全般の実装はClaudeのTypeScript SDKで実装するStreaming・Tool・MCPヘルパー、Agent SDK経由で構造化出力を使う場合はAgent SDKの構造化出力入門を参照してください。