Skip to content

fix(codex): stop double-billing cached input and reasoning tokens - #28

Open
lroolle wants to merge 2 commits into
mainfrom
fix/codex-cost-subsets
Open

fix(codex): stop double-billing cached input and reasoning tokens#28
lroolle wants to merge 2 commits into
mainfrom
fix/codex-cost-subsets

Conversation

@lroolle

@lroolle lroolle commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

Found by the first cross-provider field eval (claude f8e49d74 vs codex 019fa810, same workspace). Codex usage fields are subsets, not disjoint categories:

  • input_tokens includes cached_input_tokens — upstream codex-rs defines non_cached_input = input - cached (protocol/src/protocol.rs)
  • output_tokens includes reasoning tokens — OpenAI output_tokens_details.reasoning_tokens

ccx stored the raw values and ComputeCost billed all five fields separately: cached tokens billed twice (once at full input rate), reasoning billed twice at output rate. Real impact on the eval session: $101.99 displayed, $38.54 honest — 2.6x overstated, and a header reading 6.6m in for ~206k of uncached input while Claude's in excludes cache (same line, different semantics per provider).

Changes:

  • Codex backend normalizes to the Anthropic-style exclusive semantics the rest of ccx assumes (InputTokens excludes cache reads), for the session aggregate, the quick-parse stats, and per-message deltas
  • ComputeCost drops the reasoning term (subset of output at the same rate); contract documented on MessageUsage
  • CacheFormatVersion 2 → 3 so upgraded binaries reparse instead of serving the old numbers from the disk cache (this is how the bug initially appeared "unfixed" during verification)

Closes #27. Note: will trivially conflict with #26 on the CHANGELOG [Unreleased] header — resolve by keeping both entries.

Test plan

  • go test ./... green
  • Updated the three tests that asserted inclusive semantics; the token-count test now asserts the exact expected cost ($0.0205 for 400 uncached in / 200 out / 100 cached at gpt-5 rates) as the double-billing regression guard
  • Field verification: codex session 019fa810 re-traced at $38.54 / 206k in (was $101.99 / 6.6m in); claude session f8e49d74 unchanged at $93.35

🤖 Generated with Claude Code

lroolle and others added 2 commits July 28, 2026 22:47
Codex usage fields are subsets, not disjoint categories: input_tokens
INCLUDES cached_input_tokens (upstream codex-rs: non_cached_input =
input - cached) and output_tokens includes reasoning tokens (OpenAI
output_tokens_details). ccx stored the raw values and ComputeCost
billed every field separately, so cached tokens were billed twice
(once at full input rate) and reasoning twice at output rate.

On a real 36-minute session: displayed $101.99, honest $38.54 — 2.6x
overstated. The header also read "6.6m in" for ~206k of uncached input,
while Claude sessions' "in" excludes cache — the same line meant
different things per provider.

The Codex backend now normalizes to the Anthropic-style exclusive
semantics the rest of ccx assumes (input excludes cache reads), for
both the session aggregate and per-message deltas; ComputeCost drops
the reasoning term (subset of output, same rate); the contract is
documented on MessageUsage. CacheFormatVersion bumped so upgraded
binaries reparse instead of serving stale cached numbers.

Closes #27

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codex: cost double-bills cached input and reasoning — both are subsets, not additions

1 participant