AI how-to notes · 2026-09-17 · last run 2026-09-17 · 한국어 원문
Writing codex exec results to a file — -o and --json measured on Windows (Codex CLI)
One line summary
If you only need the final answer from codex exec, use -o FILE; if you want the whole run in machine-readable form, use --json > FILE. The two work together. In this round, pointing -o at a directory that did not exist made the file write fail while the exit code was still 0 — so check the result separately.
Who this is for
- Anyone calling
codex execfrom a script or an AI agent and handing the answer back as a file - Anyone who wants the answer alone, without the banner that gets printed to the screen (
OpenAI Codex v...,tokens used) - Anyone who wants to see what
--jsonoutput actually looks like before wiring it up
Test environment
| Item | Value |
|---|---|
| OS | Windows 11 Home (10.0.26200) |
| Shells | Git Bash, PowerShell 7.6.6 |
| codex | codex-cli 0.153.4 |
| Model (as shown in the run banner) | gpt-6-astra |
| Prompt | Reply with the single word OK. (identical in every round) |
| Shared options | -s read-only --skip-git-repo-check (Git Bash rounds 1–4 closed stdin with < /dev/null) |
This note has no comparison for the case where stdin is left open. The runs where codex exec never finished because stdin was attached to a pipe, the way an agent shell leaves it, are written up separately in codex exec hangs waiting on stdin.
The options in the help text
Two lines from codex exec --help matter here.
--json
Print events to stdout as JSONL
-o, --output-last-message <FILE>
Specifies file where the last message from the agent should be written--json is not a file option. It goes to standard output, so redirect it with > if you want it in a file.
1. Getting only the final answer with -o
codex exec -s read-only --skip-git-repo-check -o last.txt "Reply with the single word OK." < /dev/nullExit code 0, 8.4 seconds. The output splits in two: standard output carried only the answer OK, and the banner and the progress log all went to standard error.
Reading additional input from stdin...
OpenAI Codex v0.153.4
--------
workdir: C:\Users\User\AppData\Local\Temp\longtail_A\exec
model: gpt-6-astra
provider: openai
approval: never
sandbox: read-only
...
codex
OK
tokens used
6,707last.txt was 2 bytes. There is no trailing newline, so wc -l counts it as 0 lines.
0 2 last.txt
--- cat -A ---
OKcat -A shows no end-of-line marker ($) either. Worth knowing if your script concatenates this file onto another one, or judges success by line count.
2. Getting every event with --json
codex exec -s read-only --skip-git-repo-check --json "Reply with the single word OK." < /dev/null > events.jsonlExit code 0, 6.1 seconds. Standard error held only the one Reading additional input from stdin... line; no banner. The file was 4 lines, 345 bytes.
{"type":"thread.started","thread_id":"01a0ae78-dd26-7ce2-8b79-6da03f94ac20"}
{"type":"turn.started"}
{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"OK"}}
{"type":"turn.completed","usage":{"input_tokens":18453,"cached_input_tokens":12416,"cache_write_input_tokens":0,"output_tokens":5,"reasoning_output_tokens":0}}The answer lives in text on the line whose type is item.completed and whose item.type is agent_message. Token usage sits in usage on the final turn.completed line.
command -v jq printed no path in the shell we ran this from, so we pulled the answer out with Node.
node -e 'const fs=require("fs");for(const l of fs.readFileSync("events.jsonl","utf8").split(/\r?\n/)){if(!l)continue;const e=JSON.parse(l);if(e.type==="item.completed"&&e.item.type==="agent_message")console.log(e.item.text)}'OK3. Using both at once
codex exec -s read-only --skip-git-repo-check --json -o both_last.txt "Reply with the single word OK." < /dev/null > both_events.jsonlExit code 0. both_last.txt was 2 bytes of OK and both_events.jsonl was 4 lines and 345 bytes — the same shapes as sections 1 and 2, produced in a single run.
4. In PowerShell 7
codex exec -s read-only --skip-git-repo-check --json -o ps_last.txt "Reply with the single word OK." > ps_events.jsonl
"codex exit=$LASTEXITCODE"
Get-Content ps_events.jsonl | ForEach-Object { $_ | ConvertFrom-Json } |
Where-Object { $_.type -eq 'item.completed' } | ForEach-Object { $_.item.text }We ran this script with pwsh -NoProfile -File. We did not record what state stdin was in outside it. We got codex exit=0, a 2-byte ps_last.txt, and OK extracted from a 4-line ps_events.jsonl. The first 3 bytes of the file were 7B 22 74 ({"t), so no BOM.
The sizes differed. Same 4 lines, but the PowerShell file was 349 bytes. We counted CR characters.
CR count:
events.jsonl 0
ps_events.jsonl 4Only the PowerShell round's file had CR characters — 4 of them — and it was 4 bytes larger than the Git Bash one. This record alone can't settle whether PowerShell's > changed the line endings. Either way, anything reading the JSONL that splits on \n alone will be left with a trailing \r, so splitting on /\r?\n/ like the Node example above is the safer habit. The -o file was 2 bytes in both shells.
Results
| Round | Shell | Options | Exit code | Time | Files produced |
|---|---|---|---|---|---|
| 1 | Git Bash | -o last.txt | 0 | 8.4s | last.txt 2 bytes, 0 lines (no newline) |
| 2 | Git Bash | --json > events.jsonl | 0 | 6.1s | 4 lines, 345 bytes |
| 3 | Git Bash | --json -o together | 0 | 8.4s | 2 bytes + 4 lines, 345 bytes |
| 4 | Git Bash | -o nodir/last.txt (directory does not exist) | 0 | 6.1s | none |
| 5 | PowerShell 7 | --json -o together | 0 | 8.3s | 2 bytes + 4 lines, 349 bytes (4 CRs) |
What failed
Round 4 is the part of this note to be careful about. We gave -o a directory that did not exist; the answer still came out on standard output as OK, but the file write failed. This line appeared on standard error only (this PC runs Korean-locale Windows, so the OS error text is in Korean — it reads "the specified path could not be found").
Failed to write last message file "nodir/last.txt": 지정된 경로를 찾을 수 없습니다. (os error 3)And codex's exit code was still 0 (codex exit=0). That status alone cannot establish a successful save. The earlier suggestion, test -s ... || echo ..., can also print an error and still exit 0; an old file can make it pass incorrectly.
Newly checked — 2026-09-28
Save the complete Bash example verify-new-output.sh in the current directory, then run it. It requires Bash, mktemp, cmp, and an installed, authenticated Codex CLI.
bash verify-new-output.sh outputThe script creates a new directory with mktemp -d ./codex-check.XXXXXXXX, enters it, and requests OK with -o last.txt. It preserves any nonzero Codex exit code. Even after exit 0, it requires a new regular last.txt (not a symbolic link) and uses cmp to check exactly two bytes, OK, with no newline; failure exits 1. Only success prints verified: <new directory>/last.txt. The directory is left for inspection; existing files are neither overwritten nor deleted. For a real prompt, replace the fixed OK check with the expected content or format for that task.
The new verification tested the shell guard with a mock producer, not Codex. On Linux with Bash 5.2.21, correct contents returned 0; missing, empty, wrong, newline-suffixed, and symbolic-link outputs each returned 1. A producer that wrote a correct file but exited 37 propagated 37. A stale OK file in the parent directory did not make the missing-output case pass, and every run used a different new directory. This environment has no Codex binary, login, or Windows, so actual -o behavior was not retested. The 2026-09-17 measurements above are preserved.
What we did not test
- We did not measure line endings or encoding in the
-ofile for answers that are multi-line or in Korean. The answer here was the single wordOK. - We did not look at what other event types
--jsonemits during a long run where codex executes commands. The four types we saw werethread.started,turn.started,item.completedandturn.completed. --output-schema(specifying the JSON shape of the answer) appears in the help text only; we did not run it.- We did not check whether an existing
-ofile is overwritten or appended to. - We did not check
>in Command Prompt (cmd.exe), or macOS and Linux. - We did not chase down why the numbers in round 1 (
tokens used 6,707) and round 2 (input_tokens 18453) differ. We have no way to tell whether the two are measured the same way.
Related notes
- codex exec hangs waiting on stdin
- Adding an MCP server to Codex CLI config.toml
- Installing Codex CLI on Windows
Corrected 2026-09-28: The extraction code in section 2 was reading both_events.jsonl, a file that is not created until section 3. Fixed it to read events.jsonl, the file section 2 actually creates.