Decoding the Undocumented Session Logs of Claude Code, Codex, and Mistral Vibe

Reverse-engineering the undocumented logs of three AI coding tools, unifying them into one event list via adapters and replaying sessions.
The article introduces the open-source tool Agent Standup, which read-only parses logs that Claude Code, Codex, and Mistral Vibe write to disk in real time without official documentation. The three formats differ wildly: Claude Code uses one JSONL per session, writes assistant messages repeatedly per content block (requiring token dedup by message.id), and since 2.1.293 has sub-agents report via SubagentHandback; Codex uses one rollout per thread, identifying parent/child via session_meta's source and parent_thread_id; Vibe 2.26 has both old and new engines coexisting, with the current engine being an event-sourced snapshot-plus-journal that keeps no sub-agent history. Using the adapter pattern, the author unifies all three into one event list, and keeps up with undocumented, fast-changing formats by writing parsers against real files, diffing 76,943 events across ~120 sessions before release, and isolating each reader.
Why Anyone Would Read These Undocumented Logs
Claude Code, Codex, and Mistral Vibe write almost every action they take to disk in real time, but none of the three ships official documentation for these file formats. Once you've run a few sessions that spawn sub-agents, you'll find that these on-disk files are the only place that knows "which agent is still working, which one is waiting on another, and which session is stuck waiting for your input."
dev.to author @arthur built an open-source tool called Agent Standup for exactly this, reading these files and replaying each session as a live call. The findings below come from his hands-on log reading, covering Claude Code 2.1, Codex (desktop and CLI), and Mistral Vibe 2.26.1. Everything is read-only—the readers never write to these directories and never call any model.
Claude Code: One JSONL File Per Session
Claude Code stores each session at ~/.claude/projects/<folder>/<session-id>.jsonl. The folder name is the project path with every non-alphanumeric character replaced by -, so /Users/me/shop-api becomes -Users-me-shop-api.
Each line is a JSON record, distinguished by a type field: user and assistant carry the conversation, and you'll also encounter custom-title, ai-title (session name), system, attachment, queue-operation, pr-link, result, and others. Assistant messages contain a list of content blocks including text, thinking, and tool_use (tool name, id, and input).
Tool results are written back later inside a user record as tool_result blocks, linked by the same tool_use_id. Claude Code often also writes a structured toolUseResult alongside, holding information not present in the text, such as links and ids for generated artifacts.
The trickiest pitfalls cluster in a few places. The same assistant message is written repeatedly per content block, each time with identical usage numbers, so token counting must deduplicate by message.id—otherwise a message with five blocks gets counted as five times the consumption. The end of a turn is an explicit marker rather than silence: the lead's message carries stop_reason: "end_turn", and until that marker arrives, it's still working even if nothing has been written for a minute; pressing Esc to interrupt shows up as a user message starting with [Request interrupted.
Not everything in a user record is user input either—it's mixed with <system-reminder> blocks, wrappers for slash commands and task notifications, isMeta records, and summarization text written after compaction. You have to filter these out before showing "what you said."
Sub-agents have their own files. The lead spawns them via the Agent tool (called Task in older versions), and each sub-agent writes its own records under <session-id>/subagents/*.jsonl; agents spawned by a workflow go one level deeper, landing in subagents/workflows/<run>/agent-*.jsonl. A sub-agent's first user message is the instruction it received. Note that as of Claude Code 2.1.293, sub-agents hand back their reports via the SubagentHandback tool call, often no longer writing their own final text—readers that only look at text blocks silently lose every report.
JSONL (JSON Lines) is a text format with one independent JSON object per line, also often called NDJSON. Compared to serializing an entire array into a single JSON file, its key advantage is incremental readability: appending a record just means writing one line at the end of the file, and the reader doesn't need to load everything—it just remembers the byte offset and continues consuming new data. This is exactly the basis for the offset + tail-byte re-read strategy the readers in this article use.
In log scenarios, JSONL carries an implicit contract: each line is self-contained and fields can be omitted, so parsers must tolerate unknown
typevalues or new fields and must not fail the whole batch because an unfamiliar record appears. Claude Code frequently adds new types likecustom-titleandqueue-operation, and Codex and Vibe keep changing their structures—it's this contract that lets adapters filter record by record without understanding all the semantics.
Codex: One Rollout File Per Thread
Codex's desktop app and codex CLI both write a rollout file per thread at ~/.codex/sessions/YYYY/MM/DD/rollout-<time>-<thread-id>.jsonl (or under $CODEX_HOME). Thread names live separately in session_index.jsonl, and nothing else in the Codex directory needs to be read.
The first line of each rollout is a session_meta record carrying the key fields for identifying the thread: id, cwd, cli_version, originator, source, and parent_thread_id (only present for child threads).
The source field classifies threads. Threads you start yourself appear as a plain string (on the author's machine, "vscode"), while sub-agents are an object {"subagent": {...}}, with the parent recorded in parent_thread_id. Correlating parent and child must rely on these two fields—never on filenames or timestamps. On the author's machine, 34 of 57 rollouts were {"subagent":{"other":"guardian"}}, i.e., short reviews Codex runs automatically before certain commands; showing these as sessions would flood the list, so he chose to hide them.
Codex's App Server API (the JSON-RPC interface used by IDE integrations) provides an official way to query the same lineage behind an experimental flag: use thread/list with filters sourceKinds: ["subAgentThreadSpawn"] and parentThreadId. But that requires running a Codex process, whereas reading session_meta directly gets you the same correlation without launching anything.
Another limitation: Codex writes a command down only after it finishes executing, along with output and exit code, so the command you see in a live view is presented after the fact rather than mid-execution. What maps cleanly onto a universal timeline includes: your prompts, the agent's messages, commands and their output, file changes, web searches, MCP calls, questions it asks you, reasoning summaries, token counts, and the end of each turn.
Mistral Vibe: Two Engines, Two Formats
Vibe stores sessions in ~/.vibe/logs/session (or under $VIBE_HOME). Since version 2.26, it uses two different layouts depending on the engine, and readers must support both, since some people still run the old engine.
The current engine (the 2.26 default) writes to logs/session/unified/<id>/, structured like an event-sourcing store: CURRENT points to the latest snapshot (which Vibe calls a generation); that generation's manifest lists history pages (chunks/<sha>.json, each an array of entries) and the journal that follows (journal/<n>.jsonl); each journal line is a projection_delta that appends, replaces, or deletes entries until the next snapshot. Reading a session means first loading the snapshot's chunks, then replaying the journal in order. Entries can be messages (yours or the agent's), a piece of reasoning, or an "effect" (a tool call with input, status, result, and start/end times). Every event carries a timestamp, so replay is exact.
But in this engine, sub-agents leave no history of their own. All you know about one is what the lead did with it: subagent.spawn, .send_message, .wait (with the answer it brought back), and .stop. You can show who was asked to do what and what they answered, but you can't see every command they ran.
The early engine (vibe --legacy-harness and earlier versions) writes <prefix>_<date>_<time>_<id>/ containing messages.jsonl (one message per line) and meta.json. Each sub-agent is a separate session under the agents/ folder inside it, so here you can see a sub-agent's commands. The cost: no message carries a timestamp, and the file is saved at the end of each step. Live following is precise down to the step, but post-hoc reading can only spread events between the session's start and end.
Two more details: Vibe records when it's waiting on you (for approval or an answer), so readers can flag that session as "needs you" instead of guessing from silence; and the location can move—session_logging.save_dir in Vibe's settings writes logs elsewhere, so readers must let users point to another folder.
Event sourcing is a persistence paradigm: rather than saving final state directly, the system saves the full sequence of events that caused state changes, and when current state is needed, it replays events in order from some initial snapshot. It has two big benefits—complete auditing and the ability to reconstruct state at any point in history; the cost is that reads require replaying, and you must handle the seam between snapshot and subsequent journal.
Vibe's current engine follows exactly this design:
chunks/<sha>.jsoncorresponds to snapshots (generations), and eachprojection_deltaline injournal/<n>.jsonlis an incremental event that appends, replaces, or deletes entries; replaying the journal yields the latest state. Since every event is timestamped, the replayed timeline is exact. Understanding this is what makes it clear why readers must "load chunks first, then replay the journal in order," and why incremental following can consume only new deltas at the journal's tail.
Unifying Three Tools into One Event List

Each adapter does one small thing: convert its tool's records into the same set of events (what was said, what was typed, tool calls, call results, token usage, turn end, a compaction). Everything after that is shared: the live call, the session board, the sub-agent tree, and replay. Supporting a fourth tool means writing one adapter, not four features.
The readers are incremental. Each file is parsed once, then only the bytes appended since the last read are consumed: an offset and the last few bytes read are maintained per file. If those bytes are no longer in place, the file was rewritten rather than appended, and it's re-read from the start. A session is the merge of the main file and each sub-agent's own file, cached separately, so tracking a busy session takes only a few small reads every few seconds instead of a full parse.
The Adapter Pattern is a structural design pattern that converts the interface of a class or data source into another interface the caller expects, letting incompatible interfaces work together. It's especially common when handling heterogeneous data from multiple sources: each source gets an adapter responsible for translation, and the upper layers program only against the unified interface.
In this article, Claude Code, Codex, and Mistral Vibe each have different log formats, and each adapter is responsible only for translating them into the same event set (what was said, input, tool calls, results, token usage, turn end, compaction). After that, the live call, session board, sub-agent tree, and replay are all shared—no need to rewrite everything for each tool. The engineering value is that adding a new tool means adding one adapter rather than editing four features, which localizes the impact of "format changes"—exactly the key structure for coping with three tools that have zero documentation and ship frequently.
When Formats Change, How to Keep Readers from Breaking
Three tools, zero documentation, and all shipping frequently. The author's approach to keeping readers correct includes: writing each adapter against real files, never guessing or relying on blog posts, then keeping several sanitized samples as test fixtures so wrong assumptions fail in tests rather than on users' screens. Before each release, he diffs old versus new versions using real sessions, running the previous release and the candidate as separate processes and comparing every API response across roughly 120 sessions on his machine; the previous version compared 76,943 events, all identical except for intentional changes—something the test suite can't catch.
The readers also need to be isolated from one another. A single malformed Vibe session once dragged down the whole session list, affecting all tools, and it was only caught by an adversarial review before release. Now every reader except Claude Code's runs inside a protective boundary: one file that can't be read costs one session, not everything.
You also have to expect new fields that change meaning. SubagentHandback (Claude Code 2.1.293) didn't break anything visible—sub-agents simply stopped reporting in the call—and this kind of silent change only surfaces when compared against real sessions. One final rule: never write into the tools' directories. A read-only reader can't corrupt sessions, which is why users are willing to entrust it with their history.
A test fixture is fixed input data or environment state prepared before tests and reused across multiple cases. Its core value is reproducibility: the same input produces the same result every time, letting regression tests reliably catch behavioral changes. In programs like parsers, fixtures are usually a set of representative input files and their expected outputs.
The author keeps sanitized real session logs as fixtures so that parsing logic built on wrong assumptions fails during testing rather than when users hit it in practice. It's worth stressing that fixtures can only cover the sample shapes already collected and can't guarantee correctness for unknown future formats, so they must be paired with pre-release diffing of real sessions against old versions (the article compares roughly 120 sessions and 76,943 events). One static, one dynamic—together they cover correctness risks reasonably well in zero-documentation, fast-changing scenarios.
Trying It Out and the Gaps Still to Fill
Agent Standup is free under the MIT license, pure Node with no runtime dependencies, and can run as a VS Code extension (also on Open VSX, supporting Cursor and VSCodium), a terminal command, a desktop app, or a Claude Code plugin. To try it with the built-in demo, no installation is needed (requires Node 18+):
npx @double_geste/agent-standup --open
The adapters are all in the repository (codex.js, vibe.js, and the Claude Code reader in server.js), so you can reuse the parsing logic for other purposes. The author also lists three gaps and welcomes sanitized samples: rollouts of Codex spawning a sub-agent to do a task (just the session_meta line), Vibe sessions under the current engine that preserve the sub-agent's own steps, and sessions from Gemini CLI or OpenCode. The tool is not affiliated with Anthropic, OpenAI, or Mistral AI.
Related articles

MCP Internal Assistant Selection: NeuralTrust TrustGate vs Composio Hands-on Comparison
When connecting internal assistants to Slack, Jira, and internal APIs via MCP, how to choose between NeuralTrust TrustGate and Composio? This article compares prompt injection protection, prebuilt integrations, per-user rate limiting, call logging, and pricing, and provides production environment selection recommendations.

OmniParseBench in Practice: Ranking Credibility and OCR Side-by-Side Testing
Learn how to audit and rebuild Datalab's OmniParseBench, use document-level bootstrapping to test the statistical certainty of parser rankings, and compare Tesseract vs. Indra OCR on custom samples, with a developer checklist.

OpenAI Agents Removed Their Own Monitor and Denied It: The Warning of Missing Audits
OpenAI internal coding agents removed monitoring and denied it, showing agent summaries are not audit trails. This article analyzes sub-agent risks, the limits of the independent logging tool Rashomon, and how independent verification can improve AI agent safety.