Tech11 min read

Building a Log Viewer for Codex and Claude Code Subagent Time and Tokens

IkesanContents

In a previous post, I restructured Codex’s orchestration setup to keep usage under control.
Even with that in place, single operations still occasionally dragged on for hours.

Looking at running processes on my machine gave me a rough idea of what was active.
What it couldn’t tell me was what individual subagents were actually doing inside the orchestration.
In a few repositories, I had instructed subagents to log their start and finish states to Markdown files, but relying on agent compliance meant entries were skipped or incomplete—making it impossible to maintain across every repo.

I abandoned having agents log their own status and instead built a viewer that parses the session logs Codex and Claude Code already write to disk, reconstructing subagent states and runtimes from the raw event history.
Tracing those logs also exposed exact token usage, so I aggregated data across 30 days of sessions.

Environment

ItemDetails
MachineMac mini (M4)
Codex CLI0.154.0
Claude Code2.1.274
ViewerSwiftUI interface with a Python parser script (standard library only)
Period30 days from August 19 to September 18, 2026 (257 sessions total, 129 with subagents)

Subagents Are Just Threads

At first, I used ps and lsof to scan for processes, generating a list by comparing workspace directories and log modification timestamps.
That showed where a task was running.
However, even when Codex was actively orchestrating multiple subagents, only a single codex process ever appeared.

Checking the open file descriptors of that process via lsof revealed that it had six session log files (rollout-*.jsonl) open simultaneously under ~/.codex/sessions/.
Subagents are not child processes; they are threads inside the same process, and each thread writes its own rollout log.
The first line of each rollout (session_meta) specifies the parent-child relationship.

FieldExampleDescription
parent_thread_idParent’s thread IDLinks child to parent
agent_path/root/review_workerRole name assigned by parent
agent_nicknamePasteurCodex’s internal agent nickname
source.subagent.thread_spawn.depth1Depth relative to parent

The same rollout records what the subagent executed: invoked commands, files edited with apply_patch, subagent waits with wait_agent, and completion reports with runtimes via task_complete are all stored in plaintext.
Only the prompt text passed by the parent (message inside spawn_agent or send_message) was not preserved in plaintext.

For Claude Code, subagents spawned via the Agent tool write to <session-id>/subagents/agent-*.jsonl in the conversation directory.
An accompanying .meta.json file contains the initial prompt description, agent type, model, and the parent’s toolUseId.

Categorizing Time from Logs

Every event in both log formats carries a timestamp.
By tracking state transitions chronologically, the viewer aggregates the duration spent in each state.
Runtimes for sessions when the viewer wasn’t open can be reconstructed later in the same way.

StateTriggering EventDescription
ThinkingTurn start, tool execution returnedTime until model outputs next action (includes API latency)
ToolTool call dispatchedTime waiting for tool output
Waiting on childwait_agent, sleep, or foreground subagent call in Claude CodeTime parent waited while child was actively working
Other waitSame as aboveTime parent waited when no child was actively working
Waiting for inputtask_complete, turn_aborted, end_turn, interruptedTime waiting for human input
flowchart TD
  A[Input received] --> B[Thinking]
  B --> C[Tool]
  C --> B
  B --> D[Waiting on child]
  D --> B
  B --> E[Waiting for input]
  E --> A

Time during which the parent finished its turn and sat idle while subagents continued working is also counted under “Waiting on child.”
This accounts for fire-and-forget background subagents in Claude Code where the parent doesn’t hold an active wait call.
The sum of all categories matched total process uptime across all 257 sessions without discrepancy.

A single rollout log can reach 17MB, and reparsing entire files every three seconds caused severe lag.
The viewer maintains processed byte offsets and accumulated state, seeking directly to the end and parsing only appended increments.

Counting Adjustments

Applying initial logic to real session data exposed several edge cases that required adjustments.

Initial LogicInitial ResultWhat Actually HappenedFix
Child wait only during wait_agentParent active 99% in a 21-child sessionParent waited with 49 sleep and 76 list_agents callsCount sleep as child wait. Corrected to 82% active, 13% child wait
Claude subagents complete on end_turn50% completion rate, 1h 34m thinking per turn167 of 300 recent subagents ended with plain text responses without writing end_turnTreat trailing text response as completion. Rate rose to 100%
Only inspect open rolloutsParent had 1h 19m of “Other wait”Codex closes completed subagent rolloutsInclude closed subagent rollouts from the same session. Other wait dropped to ~0
Count Claude tokens once per responseOutput numbers were too lowResponses were split across multiple lines, and in 604 of 1,386 instances, values increased across linesTake the final line’s value per response

I also investigated why Codex subagents reported such tiny tool durations.
Codex runs long commands in the background and periodically polls output via write_stdin.
Aggregating two gpt-6-astra subagents showed that the duration before polling calls was under 1% of total time.
Thinking time was overwhelmingly model inference latency.

Breakdown of a Single Session

I examined a session where Codex (parent running gpt-5.6-sol medium) processed a batch of outstanding backlog tasks in a production repository.

Live tab. Redacted for publication to mask folder names, titles, and commands

The Live tab groups agents by workspace directory, placing subagents directly under their parent Codex process.
The parent row displays subagent counts by status (completed, interrupted) along with warning flags.
A horizontal stacked bar underneath shows the runtime breakdown (thinking, tool, child wait, input wait), flanked by token counters on the right.
The screenshot above uses a redacted view that masks folder names, task titles, subagent identifiers, commands, and completion summaries.

Aggregating this session at capture time produced the following breakdown:

MetricValue
Total time6h 33m
Active (thinking + tool)1h 5m (17%)
Waiting on child4h 28m (68%)
Waiting for input59m (15%)
Subagents10 total (8 completed, 2 interrupted), max 2 concurrent
Input tokens193.8M (98% cache hit)
Output tokens668k (parent 129k, children 538k)

The parent spent 4 hours and 28 minutes—roughly 70% of the entire 6-hour-33-minute session—waiting for subagents to finish.
Input tokens totaled 193.8M (~194 million tokens), of which 98% hit the prompt cache. Output tokens totaled 668k (~668 thousand tokens), with subagents accounting for 80% of that volume.
Breaking down subagent activity by model produced the following table:

Subagent ModelCountCallsThinking / CallThinking TotalTool TotalOutput Tokens
gpt-5.6-luna max420612s40m1m104k
gpt-5.6-sol high348510s80m11m187k
gpt-6-astra high351519s161m12m247k

The three gpt-6-astra high subagents had roughly the same number of invocations as sol high, but their per-call thinking latency was nearly double.
Out of 281 total minutes of child thinking time, astra accounted for 161 minutes.

30-Day Historical Data

The History tab aggregates past sessions from stored logs, categorizing them by parent configuration and child model.
Because metrics are cached independently, the data persists even if raw session logs are pruned.

Comparison table in the History tab

The session list shows runtime breakdowns, token consumption, subagent completion rates, and model distributions per session.

Session list in the History tab

By Parent Configuration

Sessions with subagents over the last 30 days that had at least two runs with identical parent configurations.
Percentages represent the ratio of total summed hours across sessions, rather than averages of per-session percentages.

Parent ConfigurationCountTotal Time (Median)ActiveChild WaitInput WaitChildren / SessionChild CompletionInput / Session (Median)Cache RateOutput / Session (Median)
codex gpt-5.6-sol xhigh731h 16m31%19%50%11.995%33.5M97%151k
claude claude-fable-5-1136h 18m28%3%70%27.3100%40.0M93%277k
claude claude-fable-5116h 23m24%6%70%17.099%54.2M96%627k
codex gpt-6-astra low81h 43m39%33%24%15.097%30.9M98%164k
codex gpt-5.6-sol medium76h 33m15%27%58%12.688%65.5M97%205k
claude claude-opus-5623h 14m5%3%91%27.599%42.5M97%258k
claude claude-sonnet-525m30%70%0%1.0100%809k78%27k
codex gpt-6-astra medium27h 5m63%2%34%15.0100%202.1M98%689k
codex gpt-5.6-sol high216h 43m17%58%11%152.094%423.5M97%1.8M
codex gpt-6-astra xhigh212h 51m8%25%68%29.598%189.7M96%1.0M

Input wait includes idle time where a human stepped away after issuing a command.
Excluding input wait, Codex parents (96 sessions) split their time almost evenly: 53% active work and 45% waiting on subagents.
In contrast, Claude Code parents (33 sessions) spent 85% in active work and only 15% waiting on subagents, doing most of the heavy lifting directly.

For sessions with three or more subagents, the maximum number of concurrently running subagents was a median of 4 in Codex (25 of 69 sessions had 2 or fewer) and a median of 7 in Claude Code.

By Child Model

Models with at least five subagent instances recorded.
Columns represent:

ColumnDefinition
ActiveThinking + Tool time (median per subagent)
Tool RatioPercentage of active time spent waiting for tool output
Thinking / CallTotal thinking duration divided by number of calls
Input / ChildInput tokens per subagent (includes cache hits)
Non-Cached Input / ChildInput tokens excluding cache hits
Output / Thinking MinOutput tokens generated per minute of thinking (includes reasoning tokens)
Child ModelCountCompletionActive (Median)Tool RatioThinking / CallInput / ChildCache RateNon-Cached Input / ChildOutput / ChildOutput / Thinking Min
claude claude-sonnet-5690100%5m0%1m389k79%81k17k3k
codex gpt-5.6-luna medium48797%3m10%10s1.7M95%76k9k2k
codex gpt-5.6-sol medium42496%5m4%13s4.4M97%119k18k2k
codex gpt-5.6-sol xhigh28992%4m5%13s2.8M96%113k15k2k
codex gpt-5.6-luna max9091%9m3%17s5.2M96%220k34k2k
codex gpt-5.6-terra medium7299%5m6%13s2.1M96%77k12k1k
codex gpt-5.6-sol high3488%12m5%14s9.0M97%232k36k2k
codex gpt-5.6-luna xhigh3087%13m4%13s10.9M97%304k55k3k
codex gpt-6-astra xhigh13100%11m1%34s2.3M95%111k22k1k
claude claude-opus-51292%2m1%36s365k79%76k2k678
codex gpt-6-astra high978%8m6%19s8.0M98%186k32k1k
codex gpt-6-astra medium8100%2m1%32s2.2M95%119k18k2k
claude claude-fable-57100%10m21%7s902k69%280k40k5k
codex gpt-5.6-luna low6100%56s2%7s211k92%17k2k2k
claude claude-haiku-4-5-202510015100%1m32%3s515k89%55k5k6k
codex gpt-5.6-terra high580%3m3%17s824k93%60k11k1k

Actual tool execution took up only 5% of active subagent time in Codex and 1% in Claude Code.
Over 95% of active duration was spent waiting for model responses; time spent on command execution and file operations was negligible.
Per-call thinking latency ranged between 7 and 17 seconds for gpt-5.6 models (sol, luna, terra), and between 19 and 34 seconds for gpt-6-astra.

Token Consumption

Because agents re-send previous conversation context with every invocation, input tokens scale up quickly, with the vast majority handled by prompt caching.

MetricCodex (96 sessions)Claude Code (33 sessions)
Total input tokens8,507.1M1,905.8M
Cache hit rate97%95%
Total output tokens35.3M17.6M
Subagent share of output68%67%

Across 30 days, total input reached 8,507.1M (~8.5 billion tokens) for Codex and 1,905.8M (~1.9 billion tokens) for Claude Code, with over 95% hitting the prompt cache in both environments.
Output totaled 35.3M tokens for Codex and 17.6M tokens for Claude Code, with subagents generating roughly two-thirds (67–68%) of all output volume.

Subagent input ranged from 0.2M to 10.9M tokens per instance, while non-cached input sat between 17k and 304k.
Output per subagent ran between 2k and 55k tokens. Output generated per minute of thinking was remarkably consistent across Codex models at 1k to 3k tokens.
gpt-6-astra generated 1k to 2k tokens per minute, sitting at the bottom of that band.

Token counting semantics differ between runtimes:

RuntimeAccounting Method
CodexFinal total_token_usage inside token_count per thread. Remains cumulative across context compressions (compact) and matches the sum of turn-by-turn usage. Parent counters exclude child usage
Claude CodePer-response usage. Total inputs include both cache creation and cache read hits

Limitations of the Numbers

Key constraints to keep in mind when interpreting these numbers:

LimitationDetails
Workload varianceThis is not an A/B benchmark running identical tasks. Task scope and human idle times varied across sessions, and parent sessions running gpt-6-astra only had 2 to 8 runs per configuration
Thinking breakdownIncludes network round-trips and API queueing; cannot isolate pure model inference time
Codex tool ratioMeasures time subagents blocked on tool returns, omitting runtimes of long background commands detached from the CLI
Log stabilityReverse-engineered from local Codex CLI 0.154.0 and Claude Code 2.1.274 output. Older logs like Codex 0.78.0 from January 2026 did not log turn boundaries and could not be parsed
Token reconciliationVerified internal consistency across log events, but not reconciled against billed invoices