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の経緯と、原因についての回答、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が空配列になっている点だけです。issueに貼られた実際のレスポンスは次のとおりです(usageの一部を省略)。
{
"model": "claude-opus-4-6",
"type": "message",
"role": "assistant",
"content": [],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 1616,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens": 6,
"service_tier": "standard"
}
}stop_reasonがend_turnなので、APIから見ればこの応答は正常に完了したターンです。output_tokensは6しかなく、スキーマに沿ったJSONが組み立てられた形跡はありません。
呼び出し側がcontent[0].textをそのままJSON.parseする実装だと、空配列への添字アクセスでundefinedを渡すことになり、パースが失敗します。
報告されたリクエストの形
issueには、報告者が空応答を再現できる最小のリクエストが貼られています。短い入力ではなく、実運用に近い構成です。
issue #913の最小再現リクエスト
モデル
claude-opus-4-6
max_tokens
4096
temperature
0.1
cache_control
3か所
ユーザーメッセージは「connect to cdn」に小さなJSON風の文脈を添えた短文です。長いsystemプロンプトに、ドメイン情報を引くgetDomainInfoツールが1つ付き、そのうえでoutput_config.formatにJSON Schemaを指定しています。cache_controlはユーザーメッセージ、system、ツール定義の3か所に付いています。
公式ドキュメントには、この構成と関係する挙動が3つ書かれています。
- structured outputsを使うと、出力形式を説明する追加のsystemプロンプトがClaudeに自動で渡される
output_config.formatを変えると、その会話スレッドのプロンプトキャッシュが無効になる- 初めて使うスキーマは文法のコンパイルで遅くなり、コンパイル結果は最後の使用から24時間キャッシュされる。ツールの集合を変えたときも、このキャッシュは無効になる
ツール定義とスキーマを併用するリクエストは、この3点がすべて関わる構成です。ただしissueの報告者も回答者も、これらが空応答の引き金だったとは述べていません。因果は示されていないので、再現リクエストの構成として知っておく情報にとどまります。
原因はモデル/API側にあった
2026年8月5日、SDKリポジトリのコントリビューターがissueにコメントを付け、原因はSDKではなくモデル/API側だったと説明しました。
当時のclaude-opus-4-6には、JSON Schemaをoutput_configで指定すると、テキストを出さないままターンを終えることがあったといいます。コメントによれば、その挙動は上流で修正済みです。報告者の最小リクエストを再実行すると、毎回スキーマに沿ったcontentが返り、thinkingの有効化やモデル変更は不要だと書かれています。claude-sonnet-4-6も同様に直ったとされています。
structured outputsの対応モデル一覧を見ると、claude-opus-4-6は今もJSON outputsの対応モデルとして載っています。同じ一覧にclaude-sonnet-4-6とclaude-sonnet-5もあります。Claude APIでは一般提供(GA)で、Amazon Bedrockでは旧来の「Amazon Bedrock(Opus 4.6以前)」統合で使えます。
issueの経緯を年表で追う
issue #913の流れ
- 2026年2月17日issueが報告される
モデルはOpus 4.6で、ほぼ毎回contentが空になる。
- 2026年5月5日別ユーザーが同じ症状を報告
OpusとSonnet 4.6の両方で起きたとのコメント。
- 2026年8月5日原因の説明とissueのclose
モデル/API側の問題で、上流で修正済みとの回答。
- 2026年8月31日Sonnet 5で類似の症状
thinkingのログが出た直後にcontentが空になるという報告。
- 2026年9月3日Sonnet 4.6とOpus 4.6で類似の症状
別のユーザーが、両モデルで同じ症状が出ているとコメント。
コメントは全部で4件で、最新は9月3日です。issueはcloseされたままで、このコメントにAnthropic側の返信は付いていません。
8月末以降の再発報告をどう読むか
8月5日の回答のあとに続いた2件の報告では、対象モデルにSonnet 5とSonnet 4.6が加わっています。
同じ「contentが空」でも、8月31日と9月3日の報告はjson_schemaの指定有無を書いていないため、2月の症状と同じ原因かは判断できません。
一方、8月31日の報告者が書いているのは「thinkingのログ直後にend_turnで終わり、空の配列を含むオブジェクトが返る」という状況です。元のissueはthinkingを有効にすると空応答が出ない症状だったため、条件が違います。別の引き金による似た症状という見方も成り立ちます。
closedのままの追加コメントについて、公式が解決済みと判断しているのか、見落とされているのかは、外から見分けられません。この点で、コード側の検査が必要になります。
公式ドキュメントが定義する正規のケースとの違い
structured outputsの公式ドキュメントは、出力がスキーマに一致しない場合を3つ挙げています。issue #913の空のcontent配列は、そのどれにも当たりません。
| 場合 | stop_reason | 特徴 |
|---|---|---|
| 拒否(refusal) | stop_reasonrefusal | 特徴安全上の理由で拒否メッセージが返り、スキーマに従わない。200ステータスで課金される |
| トークン上限到達 | stop_reasonmax_tokens | 特徴出力が途中で切れ、不完全なJSONになる |
| enum値の大文字小文字のゆれ | stop_reason通常どおり(end_turn等) | 特徴値の先頭文字の大文字小文字だけが異なる。エラーも特別なstop_reasonもない |
| issue #913の症状 | stop_reasonend_turn | 特徴contentが空で、output_tokensも数個程度 |
enum値のゆれはcontentが生成されており、公式は比較を大文字小文字を区別せずに行うよう案内しています。
issue #913の症状だけが、正常終了のラベルを付けたまま中身がありません。
なお、スキーマのプロパティにモデルの思考や推論過程を書かせる項目を入れると、reasoning_extractionによる拒否になることがあります。その場合は短い説明を求める項目に変えるよう公式が案内しています。空のcontentとは別の経路で、stop_reasonはrefusalになります。
回避策と、使えない回避策
issue本文には、空応答が出なくなったとされる回避策が3つ載っています。
| 回避策 | 内容 | 備考 |
|---|---|---|
| モデルをOpus 4.5に切り替える | 内容claude-opus-4-6をclaude-opus-4-5に変える | 備考同じリクエストで空応答が出なくなったという報告 |
| thinkingモードを有効にする | 内容Opus 4.6のままthinkingを有効にする | 備考空応答が出なくなったという報告 |
| output_configを外す | 内容output_config.formatをリクエストから外す | 備考スキーマの強制が失われる |
どれも報告者が自分のリクエストで試した結果です。恒久対策として案内されたものではなく、8月5日の回答ではthinkingの有効化やモデル変更は不要とされています。8月末以降の報告を踏まえると、手元に置いておく価値はあります。
Sonnet 5の報告では、空のcontentの前にthinkingのログがすでに出ています。thinkingの有効化は元のOpus 4.6の症状に対する回避策なので、同じ手がそのまま使える状況ではありません。
もう一つ、効かない手段があります。公式ドキュメントはJSON outputsとメッセージのprefill(最後のassistantメッセージの先頭を書き込んで出力を誘導する手法)を非互換としています。空応答を避ける目的でprefillを足す方法は選べません。citationsを有効にしたリクエストにoutput_configを付けると、400エラーになります。
コード側で空応答に備える
回避策に頼らず、レスポンスを受け取った直後に3つを順に確かめます。順序は、専用のstop_reasonを先に除外し、最後に残った空のcontentを異常として扱うものです。
パースの前に確かめる順序
- 1
stop_reasonがrefusalか
拒否です。スキーマには従わないので、パースせずに拒否として処理します。
- 2
stop_reasonがmax_tokensか
出力が途中で切れています。
max_tokensを上げて再試行します。 - 3
contentが空か
end_turnなのに空なら、上の2つのどれでもない異常応答です。リトライするか、回避策を試します。
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") {
throw new IncompleteOutputError(res);
}
if (res.content.length === 0) {
// end_turnなのにcontentが空。issue #913と同型の異常応答
throw new EmptyContentAnomalyError(res);
}三つ目の分岐で投げる例外だけをリトライ対象にすれば、拒否やトークン不足を繰り返し送信することを避けられます。失敗の種類がログから追えるので、モデル側の異常がいつ再発したかも後から数えられます。
issueの報告者は、レスポンスのid(msg_で始まる値)とusageをそのまま貼っています。空応答のログにもこの2つを残しておくと、報告するときに同じ材料を示せます。
まとめ
issueがcloseされていることは、空応答が起きないことの保証にはなりません。json_schemaを使うコードには、stop_reasonの確認とcontentの長さの確認を残しておくのが現実的です。
output_config.formatの基本的な実装手順はClaude JSONモードの使い方にあります。JSON Schemaの制約と、拒否・トークン上限・enum値ゆれの詳細はStructured outputsのJSON Schema制限をSDKが自動変換する仕組みで読めます。
TypeScript SDK全般はClaudeのTypeScript SDKで実装するStreaming・Tool・MCPヘルパー、Agent SDK経由で使う場合はAgent SDKの構造化出力入門が参照先です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。