ライフスタイル
JSON 出力と結果の検証
イベント列と最終結果を区別し、構造化出力を解析して不足項目や不正形式を検出します。
読了目安 20 分 · 操作 25 分

この記事の目次
Codex 学習目次に戻るCodex 学習ガイド:全記事の目次導入と最初のタスクから MD の指示、高度な連携まで、60 レッスン・十単元を予定しています。習熟度、環境、目的、コマンドで次の記事を探せます。未公開の記事には状態を表示します。記事全文を読む
目標と教材
この段落の教材・資料: codex execcodex exec とスクリプト連携codex exec は非対話で一回のタスクを実行します。通常は stderr に進捗、stdout に最終回答、--json では一行ずつ JSON のイベントが出ます。記事全文を読む
手順1:イベントと最終回答を分ける
--json は stdout を JSONL、つまり1行ごとに独立した JSON オブジェクトにします。単一の配列でも最終回答だけでもないので、events.jsonl 全体を1回の json.loads に渡しません。--output-schema は最終回答の形を指定し、-o はその回答を final.json に保存します。1回でイベントと構造化回答を残し、stderr に診断を保存できます。拡張子は名前であり、形式はフラグと内容で決まります。
| ファイル | 内容 | 合格条件 |
|---|---|---|
| status.json | ラッパーが記録した終了状態 | exited かつ exit_code 0 |
| events.jsonl | CLI の行単位イベント | 各行を解析でき、完了があり失敗がない |
| final.json | 最終構造化回答 | 欄・型・版・値を検証 |
| stderr.log | 進行と診断 | 調査用で回答ではない |
| schema.json | 今回要求した形式 | 期待する契約と保存 |
これらの名前は教材の約束で、Codex が常に自動作成するファイル一覧ではありません。
手順2:固定の入力と契約を用意する
自分の空の exec-json-lab で git init を実行し、下記 tasks.md を UTF-8 で作成します。前編の教材を使うなら完全一致を確認します。異なる版の混用を防ぐため検証器は exec-practice-1 だけを受け付けます。手で総数3、完了1、未完了2を確認します。実データに変える場合は新しい版と検証契約を定義し、何でも通るまで検証を削除しません。
# Practice tasks
Revision: exec-practice-1
- [x] Read the guide
- [ ] Create a practice file
- [ ] Verify the result
手順3:1回実行して段階的に検証するプログラム
下記全体を同じフォルダーの run_summary.py に保存します。標準ライブラリだけなので pip install は不要です。新しい出力フォルダー、schema、running 状態を保存してから引数配列で Codex を起動し、モデル文字列から shell 命令を組み立てません。120秒は教材の設定で Codex 固定制限ではありません。タイムアウト・起動失敗・非0終了は証拠を残して回答を不採用とし、自動再試行しません。
Windows では単体版の codex.exe を使います。PowerShell で Get-Command codex -All を実行し、インストール済み .exe を確認します。npm の .cmd/.ps1 ラッパーや別名しかない場合、Python は PowerShell と同じ方法でコマンドを探しません。Windows のインストールWindows CLI の導入と問題解決PowerShell で導入とログインを行い、実行パス、最初の読み取り、更新時の問題を確認します。記事全文を読むから公式の単体版を導入し、新しいターミナルで再確認します。対処として shell=True やシェル文字列の連結に変更しません。macOS/Linux はターミナルと Python の PATH が同じことを確認します。
"""Run once, retain evidence, and accept only a validated practice summary."""
import argparse
import json
from pathlib import Path
import subprocess
import sys
SCHEMA = {
"type": "object",
"properties": {
"revision": {"type": "string"},
"total": {"type": "integer", "minimum": 0},
"completed": {"type": "integer", "minimum": 0},
"pending": {"type": "integer", "minimum": 0},
},
"required": ["revision", "total", "completed", "pending"],
"additionalProperties": False,
}
PROMPT = (
"Read only tasks.md. Return its Revision marker and checkbox counts "
"as revision, total, completed and pending. Do not edit input files "
"or use external services."
)
def verify(run):
status = json.loads((run / "status.json").read_text(encoding="utf-8"))
if (not isinstance(status, dict)
or status != {"state": "exited", "exit_code": 0}
or type(status.get("exit_code")) is not int):
raise ValueError("Process did not exit successfully")
events = []
for number, line in enumerate((run / "events.jsonl").read_text(encoding="utf-8-sig").splitlines(), 1):
if not line.strip():
continue
event = json.loads(line)
if not isinstance(event, dict) or not isinstance(event.get("type"), str):
raise ValueError(f"Invalid event on line {number}")
events.append(event["type"])
if (events.count("turn.completed") != 1 or events.count("turn.started") != 1
or events.index("turn.started") > events.index("turn.completed")):
raise ValueError("Missing or ambiguous completed turn")
if "turn.failed" in events or "error" in events:
raise ValueError("Failure event requires investigation")
result = json.loads((run / "final.json").read_text(encoding="utf-8-sig"))
if not isinstance(result, dict) or set(result) != set(SCHEMA["required"]):
raise ValueError("Unexpected or missing final fields")
if result["revision"] != "exec-practice-1":
raise ValueError("Unexpected input revision")
if any(type(result[key]) is not int or result[key] < 0 for key in ("total", "completed", "pending")):
raise ValueError("Counts must be nonnegative integers, not booleans")
if result["total"] != result["completed"] + result["pending"]:
raise ValueError("Inconsistent count arithmetic")
return result
def main():
parser = argparse.ArgumentParser()
parser.add_argument("run_name")
parser.add_argument("--verify-only", action="store_true")
args = parser.parse_args()
root = Path.cwd().resolve()
if Path(args.run_name).name != args.run_name or args.run_name in {".", ".."}:
parser.error("Use a folder name, not a path")
run = root / args.run_name
if not args.verify_only:
if not (root / "tasks.md").is_file():
parser.error("Missing tasks.md in the current folder")
run.mkdir() # Refuse to overwrite an earlier run.
schema_path = run / "schema.json"
schema_path.write_text(json.dumps(SCHEMA, indent=2) + "\n", encoding="utf-8")
status_path = run / "status.json"
status_path.write_text(json.dumps({"state": "running"}) + "\n", encoding="utf-8")
command = ["codex", "exec", "--sandbox", "read-only", "--ephemeral", "--json",
"--output-schema", str(schema_path), "-o", str(run / "final.json"), PROMPT]
try:
with (run / "events.jsonl").open("xb") as stdout, (run / "stderr.log").open("xb") as stderr:
process = subprocess.run(command, cwd=root, stdout=stdout, stderr=stderr, timeout=120, check=False)
status = {"state": "exited", "exit_code": process.returncode}
except subprocess.TimeoutExpired:
status = {"state": "timeout"}
except OSError:
status = {"state": "launch_failed"}
status_path.write_text(json.dumps(status) + "\n", encoding="utf-8")
result = verify(run)
# Output data only: never execute text returned by the model.
print(json.dumps(result, ensure_ascii=False))
if __name__ == "__main__":
try:
main()
except (OSError, ValueError) as error:
print(f"Not accepted: {error}", file=sys.stderr)
sys.exit(1)
手順4:実行して内容を確認する
py -3 --version
py -3 run_summary.py run-01
$practiceExit = $LASTEXITCODE
$practiceExit
python3 --version
python3 run_summary.py run-01
practice_exit=$?
printf '%s\n' "$practice_exit"
Windows に py がなく python --version が Python 3 なら py -3 を python に替えます。版 exec-practice-1、total 3、completed 1、pending 2 のオブジェクトと0終了を期待し、run-01 の5ファイルを確認します。検証器は欄の不足・追加、負数、整数の代わりの true、計算不一致を拒否します。ただし3/2/1は合計が合っても内容が誤りなので、一覧との照合も必要です。schema 適合だけで事実の正しさは証明できません。
手順5:モデル利用のない3つの失敗実習
ファイル管理画面で run-01 全体を run-bad-fields にコピーし、コピー final.json の pending だけを削除します。下記 verify-only は stderr に Not accepted を出し、非0終了するはずです。元の run-01 を別に run-bad-events へコピーし、イベントオブジェクト1つの最後の閉じ波括弧を消してそのフォルダーを検証します。これも失敗します。3つ目の run-bad-status は status.json の exit_code だけ7にし、正しい final.json でも拒否されることを確認します。誤りを重ねず、各例は不変の原本から作ります。
py -3 run_summary.py run-bad-fields --verify-only
$LASTEXITCODE
python3 run_summary.py run-bad-fields --verify-only
echo $?
応用:形式は合格、内容は不一致
元の run-01 を未使用の run-wrong-counts に複製し、final.json だけ次のオブジェクトへ置き換えます。status と events を保ち、同フォルダーで verify-only を実行します。型・版・合計が適合するので終了 0 でも、tasks.md との照合では拒否します。この検証器は入力を再読せず、最終ファイルとイベント内回答の同一性も照合しないため、一般的な事実検証器ではありません。
{
"revision": "exec-practice-1",
"total": 3,
"completed": 2,
"pending": 1
}
「構造合格/内容拒否」と記録し、元の記録や後続処理に反映しません。未変更 run-01 の検証と 3/1/2 を再確認します。次の CI 篇では固定教材の正確な値を比較して、この採用条件を実行可能にします。
復元・制限・次の学習
復元のため Codex に JSON 修正を頼みません。失敗コピーを証拠として残し、元の run-01 を再検証すると合格するはずです。次の実行は run-02 とし、既存 run-01 の上書きは拒否されます。launch_failed は同じ端末で codex --version を確認し、timeout は自動化の復旧自動化の失敗、再試行、停止実行記録で既存の結果を確認し、重複を避け、前提を直して停止を確かめます。記事全文を読むを読んでから再試行を判断します。このラッパーは1回の起動を管理するだけで、ホストの全子孫プロセスや分散実行ロックを管理しません。
CLI イベントは増える可能性があるため未知種を保存し、固定 item 数で成功を決めません。未完了や失敗を含むターンは調査のため保守的に拒否します。1ターンの検証であり、任意の再開済み複数ターン記録には直接適用しません。Windows・macOS・Linux は同じ Python を使い、携帯でローカルスクリプトは動かしません。Python/CLI 版、入力版、ファイル、手計算を記録して CICodex を CI に組み込む入力、権限、終了状態を明示する CI を設計し、成果を保存して提案と適用を分けます。記事全文を読むへ進みます。参考検証は模擬イベントと実際のモデル出力を区別し、教材 JSON を実 API 回答と称しません。
Codex 学習目次に戻るCodex 学習ガイド:全記事の目次導入と最初のタスクから MD の指示、高度な連携まで、60 レッスン・十単元を予定しています。習熟度、環境、目的、コマンドで次の記事を探せます。未公開の記事には状態を表示します。記事全文を読む
詳しい説明を読む
Three numbered stages: identify the starting point, perform the exercise, and verify the result. Original illustration, not a product screenshot.
同じテーマの記事
ライフスタイル
Codex 学習ガイド:全記事の目次
導入と最初のタスクから MD の指示、高度な連携まで、60 レッスン・十単元を予定しています。習熟度、環境、目的、コマンドで次の記事を探せます。未公開の記事には状態を表示します。
ライフスタイル
Worktree とタスクの分離
Worktree は一つの Git リポジトリに別ブランチの作業場所を作ります。ファイルは分かれても DB、ポート、外部サービスは共有される場合があります。
ライフスタイル
実践:小さな Web サイトを作る
brief.md から Small Steps のタスクサイトを計画・制作し、追加、完了、削除、絞り込み、ローカル保存を実装します。HTML、CSS、データ関数、画面イベント、テストを分け、Node とブラウザーで検証して再起動・復元の手順を残します。
ライフスタイル
使用量と効率:やり直しを減らす
条件、モデル設定、時間、成果を記録し、不要な再試行と過剰な文脈を減らします。
この記事を引用している記事
最新の旅の情報・ガイド

ガイド東京
東京ではどのエリアに泊まる?新宿・上野・東京駅・渋谷・浅草・池袋・銀座の七エリア比較。空港アクセス、宿泊税、荷物配送も解説
東京ではどのエリアに泊まる?新宿、上野、東京駅、渋谷、浅草、池袋、銀座の七エリアを同じ基準で比較。成田・羽田空港からのアクセス、利用できる路線、周辺にあるもの、街の雰囲気、向いている人を、比較表と山手線の概略図とともに紹介します。2026年9月に確認した東京都の宿泊税(2027年4月から3%)と、空港宅急便で荷物を送る際のルールも解説します。
- 予算
- ホテル

ガイド東京
東京の交通パスの選び方:Suica/Welcome Suica、Tokyo Subway Ticket、JR Passは買うべき?
初めての東京では、まず一人一枚ICカードで都度払い(Welcome Suicaはデポジット不要、有効期間28日)。一日に地下鉄へ4回以上乗るならTokyo Subway Ticketの72時間券2,000円を追加し、関西へ行かず東京だけならJR Passは必ず割高です。TOURIST PASMO、iPhoneのSuica、東京メトロ一日乗車券で乗れる路線・乗れない路線を決定チャートで比較。価格は2026年9月に確認。
- 交通
- 予算

ガイド東京
東京ディズニーランド・シー攻略:チケット料金、ファンタジースプリングス、ディズニー・プレミアアクセス(DPA)とスタンバイパスの使い方、初めてならどちらを選ぶ?
東京ディズニーの一日パスポートは変動価格制で、2026 年 9 月は平日の多くが 9,900 円、週末が 10,900 円。公式サイトでは毎日 14:00 に二か月後の同日分を発売します。無料のプライオリティパスは公式サイトのサービス一覧に載っておらず、待ち時間を短縮できるのは有料のディズニー・プレミアアクセス(一人一回 1,000~3,500 円)のみ。運営時間、25 周年イベント、スタンバイパス、エントリー受付、ファンタジースプリングスの利用方法、初回にランドとシーのどちらを選ぶかも解説。2026 年 9 月に公式サイトで確認しました。
- モデルコース
- 家族向け
出典
- Codex non-interactive mode · 確認日:
- CLI command reference · 確認日:
- Python subprocess documentation · 確認日: