Skip to content

Codex ≥0.148 rollouts parse as responses: 0 — every Codex session since 2026-08-21 is silently dropped from usage/dashboard #170

Description

@BenoitPlatforms

Codex ≥0.148 rollouts parse as responses: 0 — every Codex session since 2026-08-21 is silently dropped from usage/dashboard

Summary

Codex CLI changed its rollout transcript schema: the per-turn event_msg payloads
(agent_message, user_message, mcp_tool_call_end, patch_apply_end, web_search_end)
were consolidated into a single item_completed envelope carrying a typed item.

parseCodexRollout only recognises the old payload names, so every new rollout yields
responses: 0 and prompts: 0. aggregate() then discards the session wholesale:

// src/lib/usage-index.mjs
if (!rec || !rec.responses) continue;   // no assistant turn → not a session

The result is that whole days of Codex work disappear from ak dashboard, with no error
and no health warning.

Impact

On this machine, 2026-08-22 vanished from /api/usage byDay entirely — every session
that day was Codex, and all 12 were dropped. 2026-08-23 shows sessions: 0 with only a
Claude Code session's tokens surviving.

item_completed does not appear anywhere in the 4.0.0-alpha.42 source tree, so no
version currently handles this.

The regression is dated and accelerating

Codex rollout-*.jsonl entries with responses == 0, bucketed by session end-day:

Day parsed OK dropped
Aug 08 – Aug 20 all 0
2026-08-21 8 5
2026-08-22 3 20
2026-08-23 5 51

Clean cutover starting Aug 21, consistent with a Codex CLI auto-update rolling out.

Schema diff

Same histogram of event_msgpayload.type, from a pre-cutover rollout vs a current one:

payload.type before after
agent_message 24 0
user_message 4 0
mcp_tool_call_end 4 0
patch_apply_end 7 0
web_search_end 3 0
item_completed 0 1961
token_count 139 937
task_started / task_complete 4 / 3 4 / 3

Note token_count is unchanged — token accounting still parses fine. Only the turn
counting broke, which is why the failure is silent rather than obvious.

New envelope

{
  "timestamp": "...",
  "ordinal": 1234,              // new top-level field
  "type": "event_msg",
  "payload": {
    "type": "item_completed",
    "item": { "type": "AgentMessage", ... }   // PascalCase, NOT item_type
  }
}

item.type values observed in one session (7,267 items):

Reasoning            3615     CollabAgentToolCall    87
CommandExecution     2358     ContextCompaction      27
FileChange            799     Extension              13
AgentMessage          216     UserMessage             5
SubAgentActivity      145     DynamicToolCall         2

The two that matter

// item.type === "AgentMessage"   → should increment rec.responses
{ "type": "AgentMessage",
  "id": "msg_022a7e...",
  "content": [ { "type": "Text", "text": "" } ],   // capital "Text"
  "phase": "..." }

// item.type === "UserMessage"    → should increment rec.prompts
{ "type": "UserMessage",
  "id": "01a02612-…",
  "client_id": "9b5f72a6-…",
  "content": [ { "type": "text", "text": "" } ] }  // lowercase "text"

⚠️ The content-block discriminator is "Text" for AgentMessage but "text" for
UserMessage
. Easy to miss when writing the extractor.

Reproduction

Against any rollout under ~/.codex/sessions/2026/08/2[1-3]/:

const fs = require("fs");
let agent = 0, user = 0, item = 0;
for (const l of fs.readFileSync(process.argv[1], "utf8").split("\n")) {
  if (!l) continue;
  let o; try { o = JSON.parse(l) } catch { continue }
  if (o.type !== "event_msg") continue;
  const p = o.payload || {};
  if (p.type === "agent_message") agent++;
  if (p.type === "user_message") user++;
  if (p.type === "item_completed") item++;
}
console.log({ agent, user, item });

Output on a current rollout:

{ agent: 0, user: 0, item: 7267 }
=> ak computes responses=0, prompts=0 -> session DROPPED

Suggested fix

In the event_msg loop in parseCodexRollout (src/lib/usage-index.mjs, around the
existing agent_message / user_message branches), add an item_completed branch that
maps the new item types onto the existing counters:

item.type maps to existing handling
AgentMessage agent_message (rec.responses++, punchcard, assistant turn)
UserMessage user_message (rec.prompts++, prompt turn)
CommandExecution patch_apply_end / tool-call family
FileChange patch_apply_end
DynamicToolCall, Extension, CollabAgentToolCall mcp_tool_call_end
Reasoning, SubAgentActivity, ContextCompaction no counter (skip)

Keeping both code paths would let one parser handle pre- and post-cutover rollouts, which
matters because existing usage-index.json caches hold a mix. This likely also needs a
CACHE_VERSION bump so already-cached (wrongly zeroed) Codex records get re-parsed rather
than served from cache.

Secondary: sourceHealth reports ok while dropping everything

/api/usage returns:

"sourceHealth": { "codex": { "status": "ok", "reason": null } }

…while discarding 51 of 56 Codex files that day. The check appears to verify the source is
readable, not that parsing produced anything. A parse-yield signal (files scanned vs
sessions produced, or a warning when a source yields 0 sessions from N non-empty files)
would have surfaced this immediately instead of looking like "I just didn't work much."

Possibly separate: sessions counted only on their first token-day

Independent of the above, and arguably by design — flagging in case it's worth splitting out.

// src/lib/usage-index.mjs
_day: firstDay,                                     // "The day this session's tokens FIRST landed on"

if (s._day && byDay[s._day]) byDay[s._day].sessions++;

A session spanning several days is counted once, on its first day. A 3-day session shows up
as byDay["<day 3>"] = { sessions: 0, tokens: 14574533 } — nonzero tokens, zero sessions.
The comment explains the intent (sum(byDay.sessions) === totals.sessions), but the effect
is that long-running sessions are invisible on every day after their first, which compounds
the symptom above. A separate sessionsActive counter alongside sessions would preserve
the invariant while making recent days readable.

Environment

ak          4.0.0-alpha.42   (newest published — dist-tags: next=4.0.0-alpha.42, latest=4.0.0-alpha.0)
codex-cli   0.148.0
node        v26.7.0
macOS       26.6.2 (arm64)
cache       ~/.config/agentic-kit/usage-index.json — schemaVersion 9, 274 entries

Cache staleness ruled out: only 4 of 274 entries drift from their on-disk (mtime, size),
all live files mid-write. The index refreshes correctly; the records it writes are wrong.


🤖 Investigated and drafted with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions