Skip to content

Fix token counting and codex quota window mapping - #59

Merged
MisterBrookT merged 2 commits into
mainfrom
fix/token-accuracy
Sep 9, 2026
Merged

MisterBrookT merged 2 commits into
mainfrom
fix/token-accuracy

Conversation

@MisterBrookT

Copy link
Copy Markdown
Owner

Both the Claude and the Codex numbers were inaccurate, for different reasons. Verified against real local session data.

Codex

Bug Effect
Rate-limit windows mapped by primary/secondary slot name Codex uses primary for whichever window is currently authoritative. On a weekly-only plan it reports primary.window_minutes == 10080 with secondary == null, so the 7-day figure was painted onto the 5-hour ring.
codex -a untrusted app-server untrusted was removed from the approval-policy enum (0.153.x accepts only on-request/never), so the CLI exited before answering any JSON-RPC. The live query never worked; every reading silently fell back to a stale session file.
tokens_today = final cumulative total_token_usage per session That value is session-lifetime, so a session started weeks ago landed entirely in today's total, up to 14x inflation on local data. Now sums per-turn last_token_usage over events timestamped today, skipping identical snapshots re-emitted on TUI redraw.

Before/after on this machine at the same instant: used 5h 34% (stale file, wrong ring) becomes used wk 100% (prolite) (live, correct ring).

Claude Code

Bug Measured on local data
Cache tokens excluded from the total Cache is 99.3% of traffic (4.73B vs 31M input+output). The figure tracked almost nothing.
No de-duplication 68% of usage rows were repeats: resumed, branched and compacted sessions rewrite earlier turns into new files. Now keyed on (message.id, requestId), as ccusage does.
<synthetic> rows counted Local error placeholders, not billed.
subagents/ files skipped Sidechain turns are real billed usage.
Only ~/.claude/projects read Now also CLAUDE_CONFIG_DIR (comma-separated) and ~/.config/claude.

Replaying a real busy day: 290,103 becomes 5,214,460 tokens (18x).

Shared

TODAY_START is now local midnight. The UTC boundary meant "today" began at 08:00 local at UTC+8, silently discarding the whole morning.

Verification

  • Tests/quota/test_quota.py: 24 new cases, stdlib only, temp-dir fixtures, no network and no user data. Confirmed to fail against the pre-fix code (11 failures / 9 errors) and pass after. Wired into CI ahead of swift test.
  • swift build and swift test green (157 tests).
  • Live quota.py --json produces correct windows for claude, codex and cursor.

Counting rules recorded in dev_docs/integrate/usage-accounting.md.

Both the Claude and Codex numbers were wrong, in different ways.

Claude Code:
- Count cache tokens. Cache reads are ~99% of real traffic, so summing
  only input+output tracked almost nothing (measured 31M vs 4.73B).
- De-duplicate usage records on (message.id, requestId). Resumed,
  branched and compacted sessions rewrite earlier turns into new files;
  68% of local usage rows were repeats of an already-counted API call.
- Skip `<synthetic>` placeholders, which are local error rows, not usage.
- Include subagent/sidechain files, which are real billed usage.
- Scan CLAUDE_CONFIG_DIR and ~/.config/claude, not just ~/.claude.

Codex:
- Map rate-limit windows by window length, never by the primary/secondary
  slot name. A weekly-only plan reports primary.window_minutes == 10080
  with a null secondary, so the slot name painted a 7-day percentage onto
  the 5-hour ring.
- Fix the app-server invocation: `-a untrusted` is no longer a valid
  approval policy, so the live query exited before answering and every
  reading silently came from a stale session file.
- Sum today's per-turn last_token_usage instead of the final cumulative
  total, which attributed a whole multi-day session to its last active
  day (up to 14x inflation locally), and skip repeated identical
  snapshots emitted on TUI redraw.

Shared:
- "Today" now starts at local midnight. A UTC boundary discarded the
  whole morning east of UTC.

Adds Tests/quota/test_quota.py (24 cases, stdlib only, temp-dir fixtures,
no network or user data) wired into CI, plus the counting contract in
dev_docs/integrate/usage-accounting.md.
A rate_limits event that carries only a plan label is not a usable
reading, so the freshest-first walk must continue instead of stopping
there. sessions_today now uses the local day boundary, matching how
tokens are counted (the 24h file window still lets a session that ran
past midnight contribute today's events).
@MisterBrookT
MisterBrookT merged commit 33031c1 into main Sep 9, 2026
2 checks passed
@MisterBrookT
MisterBrookT deleted the fix/token-accuracy branch September 9, 2026 07:06
@MisterBrookT MisterBrookT mentioned this pull request Sep 9, 2026
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.

1 participant