CIで自動化ジョブを回していると、たまに出力のJSONが微妙に崩れる。前置きの文章が一行混じる、コードフェンスで囲まれる、キーがひとつ欠ける。パースが落ちる。そして再実行。Codexの codex exec resume が --output-schema を受け付けるようになって、この崩れがほぼ消えた。私のサンプルでは、再開ジョブのパース失敗率が約2割からゼロ近くまで落ちた。
何が変わったのか
これまで codex exec には構造化出力を指定する手段があったが、resume(中断したセッションを再開する側)では、その指定が引き継ぎにくかった。再開するとセッションの文脈は戻るのに、出力形式の縛りが緩んで、自由なテキストに戻ってしまう。ここが噛み合っていなかった。
今回の変更で、codex exec resume が --output-schema を受け付ける。再開した自動化ジョブが、セッションの文脈を保ったまま、なおかつスキーマ準拠のJSON出力を強制できる。文脈の継続と出力の厳格さ、その両方が同時に立つようになった、ということだ。
使い方
スキーマをJSONで用意する。たとえば、レビュー結果を返すジョブならこうだ。
{
"type": "object",
"properties": {
"summary": { "type": "string" },
"issues": { "type": "array", "items": { "type": "string" } },
"risk": { "type": "string", "enum": ["low", "medium", "high"] }
},
"required": ["summary", "risk"]
}
最初のジョブを投げ、途中で止まったセッションを再開するときに、スキーマを渡す。
codex exec resume <session-id> \
--output-schema ./review.schema.json
これで、再開後の最終出力が review.schema.json に従う。risk は low / medium / high のいずれかしか返らない。前置きの散文や、勝手なコードフェンスは付かない。後段の jq がそのまま通る。
実測:パース失敗率がどう動いたか
手元のCIで、PRに対して同じレビュー系ジョブを50回回して比較した。条件は、長めのコンテキストで一度中断し、resume で続きを走らせて最終JSONを受け取る、という流れだ。
--output-schema なしの旧来のやり方では、50回のうち11回、後段のパースが落ちた。理由はだいたい同じで、JSONの前に一文だけ説明が付く、あるいは全体がコードフェンスで囲まれる。率にして22%。これは無視できない。CIの赤が二割の確率で混じると、人はその赤を信用しなくなる。「また形式の崩れだろう」と。これがいちばん怖い。本物のエラーを見逃す温床になる。
スキーマを当てた側は、50回中パース失敗が0回だった。厳密にはゼロを保証はできない(LLMである以上、確率はゼロにはならない)。が、私の試行の範囲では一度も落ちなかった。22%が0%。CIの赤が、ようやく「本物の赤」だけになった。
限界と注意点
スキーマは出力の「形」を縛るだけで、「中身の正しさ」までは保証しない。risk が enum で縛られていても、その判定が妥当かは別の話だ。形式の検証と内容の検証を混同しないこと。ここを取り違えると、緑のCIに安心して中身のミスを通す。
それと、スキーマを厳しくしすぎると、モデルが必須キーを埋めるために無理な値を入れてくる場面があった。required は本当に要るものだけに絞った方が、結果の素直さは保てる。私は最初 issues も必須にしていたが、空配列を嫌って妙な項目を捏造する挙動が出たので外した。
私見
地味な引数の追加に見える。でも、自動化を運用したことがある人ほど、この一行の重さが分かるはずだ。再開と構造化が両立しなかったせいで、長尺ジョブを分割できず、無理に一発で回していた現場は多い。文脈を保ったまま途中から再開でき、しかも出力は壊れない。これで、ジョブを安心して区切れる。
構造化出力というのは、AIエージェントを「人が読む道具」から「他のプログラムが読む部品」へ変える境界線だと思っている。今回の変更は、その境界線を再開ジョブにも引いた。次は、複数の resume を直列でつないで、各段の出力スキーマを変えながらパイプラインを組んでみたい。そこまでいけば、Codexはもう対話ツールではなく、立派なバッチ処理基盤になる。

