MAX_STRUCTURED_OUTPUT_RETRIES — Claude Codeの構造化出力の再試行回数
claude -pの--json-schemaで検証失敗が続いたときの再試行回数を決める環境変数です。既定の5回の数え方、尽きたときの失敗の見え方、上げる前に直すものをまとめます。
MAX_STRUCTURED_OUTPUT_RETRIESは、claude -pに--json-schemaを渡したとき、モデルの応答がスキーマの検証に落ちた場合に何回まで試すかを決める環境変数です。既定は5回で、尽きても有効な出力が得られなければ、その実行は失敗として終わります。
この上限はコマンドラインの-pだけの話ではありません。ワークフローのサブエージェントが返す構造化出力にも、同じ上限が掛かります。
既定の5回は「初回と再試行4回」
数え方で迷いやすいので、先に押さえておきます。
既定値の中身
既定の試行回数
5回
MAX_STRUCTURED_OUTPUT_RETRIESを設定しない場合
内訳
初回1 + 再試行4
再試行回数ではなく、試行の合計
変数名にはRETRIESと付いていますが、説明にあるのは「許可する試行(attempt)の数」で、既定の5は初回の応答に4回の再試行を足した合計です。ワークフローの説明でも「5回の試行のあとで失敗する」と書かれています。「再試行を5回」と読むと、実際より1回多く見積もります。
上限が掛かる場所は3つ
| 場所 | 何が検証に落ちるか | 上限を使い切ったとき |
|---|---|---|
claude -p --json-schema | 何が検証に落ちるか最終応答がスキーマに合わない | 上限を使い切ったときその実行が失敗する |
ワークフローのagent()のschema | 何が検証に落ちるかサブエージェントの出力がスキーマに合わない | 上限を使い切ったとき直近の検証失敗を含むエラーで呼び出しが失敗する |
| Agent SDK | 何が検証に落ちるか出力がスキーマに合わない | 上限を使い切ったとき結果メッセージのsubtypeがerror_max_structured_output_retriesになる |
SDKの行は、変数の説明そのものではなく、SDKの構造化出力のページに書かれた失敗の見え方です。そのページは「上限内に検証を通らなければ、結果は構造化データではなくエラーになる」と述べていますが、上限が本記事の環境変数で変えられるとは明記していません。SDKから使うときは、変数が効くかを手元で確かめてください。
SDKから使うときの分岐
Agent SDKでは、query()のoutputFormat(Pythonはoutput_format)にJSON Schemaを渡します。結果メッセージのsubtypeがsuccessでstructured_outputが入っていれば成功、error_max_structured_output_retriesなら再試行が尽きた状態です。
for await (const message of query({
prompt: "Extract contact info from the document",
options: { outputFormat: { type: "json_schema", schema } },
})) {
if (message.type !== "result") continue;
if (message.subtype === "success" && message.structured_output) {
console.log(message.structured_output);
} else if (message.subtype === "error_max_structured_output_retries") {
console.error("再試行が尽きました");
} else {
console.error("構造化出力のないまま終了しました");
}
}分岐は3つ要ります。successだけを見てstructured_outputの有無を確かめないと、エージェントが構造化出力を作らずに終わった場合を成功と取り違えます。単発のquery()はエラー結果を流したあとに例外を投げるので、tryで囲んで接続やプロセスの失敗も受けます。
ZodやPydanticでスキーマを作るとき
SDKはJSON Schemaのdraft-07で検証するため、それより新しい版を宣言したスキーマは拒否されます。ZodのスキーマをTypeScriptで変換する場合、既定の出力はdraft 2020-12なので、z.toJSONSchema(FeaturePlan, { target: "draft-7" })のように版を指定します。指定を忘れると、モデルの応答以前にスキーマの段階でつまずくため、再試行の上限とは無関係に失敗します。
通った出力を型付きで使いたいときは、structured_outputをZodのsafeParseにもう一度通す書き方が公式の例にあります。型として扱う前に検証を一度挟む形になり、通らなかった場合の分岐も書けます。
上限を変える
シェルから1回の実行だけ変えるなら、コマンドの前に置きます。
MAX_STRUCTURED_OUTPUT_RETRIES=2 claude -p "auth.pyの関数名を抽出して" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'プロジェクトで固定したいときは、設定ファイルのenvに書きます。値は文字列で書き、.claude/settings.jsonに置けばリポジトリを共有する全員に効きます。
{
"env": {
"MAX_STRUCTURED_OUTPUT_RETRIES": "3"
}
}シェルと設定ファイルの両方で同じ変数を設定すると、ほとんどのセッションでは設定ファイル側の値が使われます。CIで値を差し替えたいのにシェルのexportが効かないときは、設定ファイルのenvを先に疑います。
環境変数の説明には、受け付ける値の範囲や、0や負の数を渡したときの扱いが載っていません。極端な値を前提にせず、小さな整数で挙動を確かめてから使います。
失敗の出方を見分ける
再試行が尽きたとき、-pの呼び出し側が読むべき点は2つあります。
- 出力に有効な
structured_outputが入っているか - 終了後のスクリプトがその有無で分岐しているか
SDKの結果メッセージは、成功ならsubtypeがsuccessでstructured_outputが入ります。失敗はerror_max_structured_output_retriesです。ただしsubtypeがsuccessなのにstructured_outputが空という場合もあり、公式はこれも失敗として扱うよう案内しています。
シェルスクリプトなら、jq -eでstructured_outputの有無をそのまま終了コードにできます。-eは結果がnullかfalseのときに非ゼロで終わります。
out=$(claude -p "auth.pyの関数名を抽出して" \
--output-format json \
--json-schema "$(cat schema.json)")
echo "$out" | jq -e '.structured_output' > result.json \
|| { echo "構造化出力が得られませんでした" >&2; exit 1; }jqの例は、公式が示すjq '.structured_output'の形に-eを足したものです。claude自体の終了コードをどう扱うかは、ドキュメントに記載がありません。失敗の判定は終了コードだけに頼らず、出力の中身でも行うと安全です。--output-format stream-jsonで受けている場合も、最後の行が最終応答のテキストやコスト、セッション情報を持つresultメッセージなので、判定はそこを見ます。
回数を増やす前に、失敗の原因を分ける
上限を上げれば、その分だけ再プロンプトが走ります。直る見込みのある失敗なら効きますが、原因が別なら5回が10回になるだけです。公式は原因を次のように挙げています。
- スキーマがタスクに対して複雑すぎる
- タスクが曖昧で、何を出力すべきか決まらない
- 検証エラーを直そうとして、上限に達した
- モデルのフォールバックで完成済みの出力が途中で取り消され、再試行でも置き換わらなかった
最後の1つは、検証に一度も失敗していなくても起こります。エラー結果のerrorsリストを見ると、検証失敗と取り消しを見分けられます。取り消しが原因なら、回数を増やしても検証の問題ではないので効果はありません。
取り消しは、モデルの自動フォールバックで起こります。Fable系モデルやOpus 5.5、Sonnet 5.5などの安全分類器は、サイバーセキュリティや生物学の話題を検知すると、リクエストをフォールバック先のモデルで再実行します。たとえばFable 5.1とOpus 5.5は、サイバーセキュリティの検知ならOpus 4.8で再実行されます。完成済みの出力が流れている途中でこの切り替えが入ると、出力が取り消され、再試行で置き換わらなければ同じエラーで終わります。脆弱性の調査やセキュリティ監査のログを構造化させるジョブで、検証には通っているのに失敗する場合は、この経路を疑います。切り替えのあとセッションはフォールバック先のモデルで続き、元のモデルに戻すには/modelを実行します。カテゴリごとの切り替えはv2.1.219以降の挙動で、それより前はFable 5の検知がすべてOpus系の既定モデルでの再実行になっていました。
検証失敗が原因なら、回数より先にスキーマとプロンプトを見直す価値があります。公式が挙げる回避策は次の3つです。
- スキーマを絞る。深いネストと多数の必須項目は満たしにくい
- タスクに情報が足りない場合に備え、その項目を省略可能にする
- プロンプトを明確にする
たとえば、記事から「著者名」を抽出させるスキーマで、著者が書かれていない記事が混ざる状況を考えます。authorが必須だと、モデルは存在しない値を作るか、検証に落ち続けるかのどちらかになります。requiredから外してnullを許せば、1回目で通ります。再試行の回数を触る前に、こうした必須項目の見直しを試す価値があります。
ワークフローでは呼び出しごとに失敗する
ワークフローのagent()にschemaを渡すと、サブエージェントはその形のJSONを返します。実行前に、Claude Codeがスキーマ自体の矛盾を確かめます。たとえばrequiredに挙げたキーがadditionalProperties: falseで排除されていれば、サブエージェントを起動せずエラーになります。
起動後に出力が検証に落ち続けると、5回目の失敗でagent()の呼び出しがエラーで終わります。エラーには直近の検証失敗が含まれるため、何が合わなかったかをそこから読めます。
ワークフローのagent()は、停止した場合や回復できないAPIエラーではnullを返します。検証の上限切れはnullではなく呼び出しの失敗として表れる点が、結果配列をfilter(Boolean)で整える書き方とは別の扱いになります。
名前の近い変数との違い
CLAUDE_CODE_DISABLE_STRUCTURED_OUTPUTSは、再試行の回数とは別の層の変数です。1にすると、構造化出力のAPIフィールドoutput_config.formatと、対になるanthropic-betaの値をClaude Codeが送らなくなります。上流がこれらを拒否するLLMゲートウェイ向けで、v2.1.288以降が対象です。詳しくはCLAUDE_CODE_DISABLE_STRUCTURED_OUTPUTSの解説にあります。
| 変数 | 効く層 | 使う場面 |
|---|---|---|
MAX_STRUCTURED_OUTPUT_RETRIES | 効く層応答の検証に落ちたあとの試行回数 | 使う場面応答は返るが、スキーマに合わない |
CLAUDE_CODE_DISABLE_STRUCTURED_OUTPUTS | 効く層リクエストに構造化出力のフィールドを載せるか | 使う場面上流がそのフィールドを拒否する |
ゲートウェイがoutput_configを弾いているなら、上流のエラーはリクエストの段階で返り、検証の再試行まで進みません。回数を増やしても直らないので、先に上の変数を確かめます。CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETASを1にしてもoutput_config.formatは送られなくなりますが、こちらはほかの先行機能もまとめて止めます(v2.1.287以降)。構造化出力だけを外したいなら専用の変数のほうが影響が小さく済みます。
判断の目安
| 状況 | 先にやること |
|---|---|
| 検証失敗の内容が毎回違う | 先にやること回数を増やす前にスキーマの必須項目と複雑さを減らす |
| 同じ項目で毎回落ちる | 先にやることその項目の型・列挙値・プロンプトの指示を直す |
errorsが検証失敗を示していない | 先にやることモデルのフォールバックの影響を疑う |
起動時にis not a valid JSON Schema | 先にやること再試行以前の問題。スキーマの直し方を参照 |
ゲートウェイ越しでoutput_configの400 | 先にやること再試行以前の問題。CLAUDE_CODE_DISABLE_STRUCTURED_OUTPUTSを確かめる |
起動時のスキーマエラーは、この変数の対象外です。v2.1.205より前は、不正なスキーマを黙って無視して構造化されていない文章を返し、formatキーワードを含むスキーマも不正として扱っていました。今はformatは注釈として受け付けるだけで、検証には使われません。つまり"format": "email"を書いても、メールアドレスの形でない値は検証で落ちず、再試行も起きません。再試行は「スキーマは正しいが応答が合わない」ときだけの仕組みなので、先にそちらの切り分けが要ります。
claude -pの出力形式の選び方やjqでの抽出は構造化出力とストリーミングの記事に、環境変数の一覧は設定リファレンスにあります。
まとめ
既定の5回は初回を含む合計で、claude -pのスキーマ検証にもワークフローのサブエージェントにも同じ上限が掛かります。値は環境変数か設定ファイルのenvで変えられますが、効くのは直る見込みのある検証失敗だけです。取り消しやスキーマ自体の問題には効かないので、errorsで原因を分けてから、必須項目の見直しに進む順序になります。