Skip to content

codex adapter: total_tokens undercounts by omitting cached_input_tokens and reasoning_output_tokens #3

Description

@fjgbue

⚠️ This was automatically generated by Javi OpenClaw BugFinder Bot. Not reviewed by a human. Verify independently. Author: 12122J. Co-author: fjbgu OpenClaw Assistant.

Severity: Medium

File & Line

src/adapters/codex.mjsobserveCodexLine() function, turn.completed event handler (approx. line 68)

Root Cause

When processing turn.completed events from Codex CLI, the total_tokens field is computed as:

total_tokens: numberOrZero(usage.input_tokens) + numberOrZero(usage.output_tokens)

This omits two token categories that Codex may report:

  1. cached_input_tokens — tokens retrieved from the context cache, which still count toward the session total
  2. reasoning_output_tokens — tokens consumed by extended thinking / reasoning models (e.g., models with reasoning/chain-of-thought capabilities)

Compare with the Claude Code adapter in claude-code.mjs, which includes both:

const total = input + cachedRead + cachedCreate + output;

Impact

Token usage is systematically under-reported for Codex sessions. For reasoning-heavy models, the undercount can be substantial (reasoning tokens often exceed output tokens). This affects:

  • Cost estimates (pricing calculations use total_tokens)
  • Session summaries (tt summarize)
  • Dashboard visualizations

Suggested Fix

if (payload.type === "turn.completed" && usage) {
  observations.push({
    type: "usage.tokens",
    input_tokens: numberOrZero(usage.input_tokens),
    cached_input_tokens: numberOrZero(usage.cached_input_tokens),
    output_tokens: numberOrZero(usage.output_tokens),
    reasoning_output_tokens: numberOrZero(usage.reasoning_output_tokens),
    total_tokens: numberOrZero(usage.input_tokens)
      + numberOrZero(usage.cached_input_tokens)
      + numberOrZero(usage.output_tokens)
      + numberOrZero(usage.reasoning_output_tokens),
  });
}

Additional Context

The usage object destructured from Codex events may use different field names than the DeepSeek/Anthropic convention. Verify that Codex uses cached_input_tokens and reasoning_output_tokens as field names in the actual event payload. If Codex uses different names, add those variants to the destructuring.

Also consider making the total_tokens computation consistent with the Claude Code adapter (claude-code.mjs), which already accounts for all token categories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions