bough
Codex All documents

The Codex CLI rollout file format

Notes from reading real Codex CLI rollout files, the same way as the Claude Code transcripts and worth their own page.

Be warned that the corpus behind this page is much smaller than the Claude one: 3 rollout files, 242 lines, 7.4 MB, from Codex Desktop 0.153.4 on one machine in September 2026. The shapes described here were each traced to code that was getting them wrong, so they are real, but the counts prove far less than the Claude figures do.

The format also moved recently and visibly. A parser written against an earlier Codex CLI read this corpus with three separate faults, described below. Treat anything here as true of 0.153.x and check it against your own files.

What will catch you out

Replay spans files, not lines. Claude Code replays inside one transcript; Codex writes a whole new rollout and replays the earlier items into it under the ids they already had. Deduplicate within a file and you catch none of it, since the first copy is in a different file. Group rollouts by session before you deduplicate, or a resumed session comes back as several sessions with its early prompts counted once per resume.

A sub-agent's rollout carries its parent's session id. Spawning an agent writes a rollout whose session_meta.session_id is the parent's, while its own id differs and parent_thread_id names the parent. Group on session_id alone and the sub-agent's prompts and tokens vanish into the agent that spawned it. Group on id alone and a resumed session splits. Both fields are needed: parent_thread_id is what tells the two cases apart.

Usage is reported twice. Newer rollouts carry a token_usage_record line per response and an event_msg of type token_count saying the same thing. Add both and every token figure in your output doubles. Prefer the record: it carries a response_id, which is what lets a replayed response be recognised as one already counted, and the event carries no id at all. Older rollouts have only the event, so it still has to be read as a fallback.

Pair a tool call with its result by call_id. Codex issues calls in parallel and the results come back interleaved, so the next output is often not the answer to the last call. Both the call and its output carry the same call_id, and that is the only thing joining them. Settle a git commit on whichever output arrives next and an unrelated command's exit code decides whether the commit counted.

The cached tokens are inside input_tokens, not beside them. A usage record reads like four separate figures and is not:

{"input_tokens":28739,"cached_input_tokens":28032,"output_tokens":11,"total_tokens":28750}

total_tokens is input_tokens plus output_tokens, which is what says the 28,032 cached are part of the 28,739 rather than additional to them. Add input_tokens and cached_input_tokens together and you have counted the cache twice: on one real project that turned 1.59M tokens into 3.04M. The fresh input is input_tokens - cached_input_tokens, which here is 707.

Claude Code reports these already separated, so a parser that reads both agents cannot use one rule for the pair.

A single user record holds several injected blocks. The desktop app sends a plugin catalogue, the environment, the permissions and more as separate input_text chunks of one user message. Test only how the joined text begins and whichever block happens to arrive first decides the answer: a record that is entirely machine-generated reads as a prompt, and every session gains one that nobody typed. Strip each known block by its own closing tag and treat the record as a prompt only if something is left.

A sub-agent's rollout has no user message at all. The work it was asked to do arrives as an agent_message from the agent that spawned it. Count only user messages as prompts and the whole session comes back empty, and an empty session is usually dropped, so the work disappears rather than being merely mislabelled.

Agents message each other in both directions. A reply from a sub-agent is also an agent_message. Treat every one as new work and the parent gains a turn each time a sub-agent reports back. Only NEW_TASK opens work; the envelope says which is which.

What one agent asks another is encrypted. The brief in a spawn_agent call and the payload of an agent_message are Fernet tokens: base64url, beginning gAAAAA, a few hundred characters with no spaces. There is nothing to read. What matters is not printing one as though it were something a person wrote, which is how a wall of ciphertext ended up as a label on a diagram here. The envelope around the payload is plain text and carries a Task name: line, which is the honest thing to show instead.

Where the files live

~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<session id>.jsonl

One file per session, in date directories. Unlike Claude Code there is no per-project directory: the project is session_meta.cwd, so grouping by project means reading the head of every file. A rollout whose cwd sits inside ~/.codex is internal rather than a project of yours.

Record types

Every line is {timestamp, ordinal, type, payload}, and type is the outer discriminator with a second type inside most payloads.

session_meta          once, first: session_id, id, parent_thread_id, cwd
turn_context          the active model, and the cwd for that turn
response_item         the conversation: message, reasoning, function_call,
                      custom_tool_call, their outputs, and agent_message
event_msg             progress: task_started, item_completed, token_count
token_usage_record    per response usage, with a response_id
world_state           the harness's own bookkeeping

response_item is where the work is. The rest is mostly noise for these purposes, with the exception of token_usage_record.

Tokens

token_usage_record carries both usage, for the response that just finished, and turn_token_usage, a running total. Sum usage. Summing the running total counts the first response once per response that follows it.

The running total resets per turn rather than accumulating across a file, so it cannot stand in for a session total either.

Sub-agents

Codex records work handed to another agent, which Claude Code does not do in the same shape. It is worth reading: a spawn_agent call names the task, the sub-agent gets its own rollout with its own prompts, tools and token spend, and that spend is part of what answering the original prompt cost.

It is not a separate stretch of work. The sub-agent runs inside one turn of the session that spawned it, usually finishing before that session's next prompt, so its natural home is that turn rather than a place beside it.

session_meta.source describes the spawn, including depth, so nesting beyond one level exists. Nothing here has measured it past depth 1.