fix(codex): preserve distinct requests when cumulative usage is missing - #1264
Conversation
ozymandiashh
left a comment
There was a problem hiding this comment.
Thanks for the fix and the cache bump. Two blockers.
-
tests/providers/codex.test.ts:1225fails (still dedups consecutive zero-cumulative duplicates: expected 1 call, got 2), on this head and on the head merged with currentmain.docs/providers/codex.md:164asks for exactly that suite plustests/parser-filter.test.tsto be run when the dedup key shape changes, and the PR template requiresnpm testto pass. -
The failing test is not stale; it is #257's deliberate decision. #257 changed the guard from
cumulativeTotal > 0toprevCumulativeTotal !== nullspecifically to stop double-counting consecutive zero-cumulativetoken_countevents. Your newtests/codex-missing-cumulative.test.tsasserts three identical consecutive events produce three calls, which reinstates exactly what #257 removed. Both sides rest on synthetic fixtures. The only thing that settles it is a real Codex rollout JSONL: does Codex ever repeat atoken_countevent for the same request? If you have one, attach a redacted excerpt (perdocs/providers/codex.md"Real data first"). If it does not repeat, delete the old test with that justification in the commit message; if it does, narrow the fix so byte-identical consecutive records still collapse.
On your question about double-counting: I probed it and all-cumulative and mixed sessions still behave like main thanks to the seenKeys backstop. The one weakened path is a forked session lacking cumulative totals outside the 5 s cutoff, where the forkedFromId replay protection no longer applies. You acknowledge the trade-off; please state it in the code comment you replaced rather than deleting the #1088 invariant note outright.
Also update docs/providers/codex.md:40-42 (dedup key shape) and the Quirks note on prevCumulativeTotal.
…ns still collapse (getagentseal#257) Real-data answer to the review's question: Codex does re-emit byte-identical token_count events. Scanned 53 public rollout sessions (codeset-ai/ codeset-release-evals, 1313 token_count events): 603 (46%) are byte-identical repeats of their predecessor (identical total AND last usage), 0 cumulative monotonicity violations, 54 events carry no total_token_usage at all. The missing-cumulative path now collapses a record whose info payload is byte-identical to the previous event's (getagentseal#257 semantics restored); only records with differing payloads count as distinct requests. prevInfoIdentity is persisted in the resume state. The getagentseal#1088 invariant note is restored at the dedupKey in adapted form; docs/providers/codex.md documents the three-layer dedup and the accepted fork-cutoff trade-off.
|
You asked for a real Codex rollout to settle it, so I scanned one: codeset-ai/codeset-release-evals (public eval corpus) — 53 sessions, 1313
Verbatim excerpt (lines 14/17 vs 24 of {"timestamp":"…T16:22:18.697Z","type":"event_msg","payload":{"type":"token_count","info":{"total_token_usage":{"input_tokens":11923,"cached_input_tokens":0,"output_tokens":208,"reasoning_output_tokens":54,"total_tokens":12131},"last_token_usage":{"input_tokens":11923,…identical…},"model_context_window":258400}}}
{"timestamp":"…T16:22:19.467Z","type":"event_msg","payload":{"type":"token_count","info":{…identical to the line above…}}}
{"timestamp":"…T16:22:22.661Z","type":"event_msg","payload":{"type":"token_count","info":{"total_token_usage":{"input_tokens":26873,…},"last_token_usage":{"input_tokens":14950,…}}}} ← next request, payload differsThat lands on your "it does repeat" branch, so 4c3a2b6 narrows the fix exactly as you specified:
|
Dismissed: both blockers verified as addressed on 4c3a2b6 — tests/providers/codex.test.ts (incl. the #257 zero-cumulative dedup test at :1201) and tests/parser-filter.test.ts pass; the fix was narrowed per your 'it does repeat' branch (byte-identical consecutive records collapse via info-payload identity, rate_limits excluded as payload sibling); #1088 invariant note restored at dedupKey; docs/providers/codex.md dedup + Quirks updated. Independent spot-check of codeset-ai/codeset-release-evals confirms the re-emission data (cited file: 9/20 byte-identical repeats, 0 monotonicity violations). Full suite green locally; CI never ran on the PR (action_required first-contributor gate).
ozymandiashh
left a comment
There was a problem hiding this comment.
Re-reviewed 4c3a2b6 — both blockers addressed exactly as specified.
- The #257 suite passes again: tests/providers/codex.test.ts 48/48 (incl. zero-cumulative dedup at :1201), tests/codex-missing-cumulative.test.ts 6/6 (both halves: identical ×3 → 1 call, differing ×3 → 3 calls), tests/parser-filter.test.ts 11/11. tsc --noEmit clean. Full npm test: 3744 passed / 0 failed (one lock-timing flake on the first run, passes in isolation on both main and this head).
- The real-data answer checks out: independently scanned the cited file from codeset-ai/codeset-release-evals — 20 token_count events, 9 byte-identical repeats (45%), 0 monotonicity violations, plus one null-info event (handled by the est path). The byte-identity collapse on info (rate_limits correctly excluded as a payload sibling) is the right narrowing.
Also verified: #1088 invariant note restored at dedupKey with the fork-cutoff trade-off stated; docs dedup + Quirks updated; prevInfoIdentity persisted in resume state; both cache layers invalidated (v16 + parse-version suffix). My previous review is dismissed as addressed.
ozymandiashh
left a comment
There was a problem hiding this comment.
Reviewed the full diff and validated it beyond CI: the patch applies cleanly on current main, and applied on a temp branch the codex suites (new missing-cumulative tests plus providers/codex and pricing-1075) pass 61/61 locally. The three-layer dedup is the right shape — byte-identity collapse for re-emissions with real-world measurements behind it (603/1313 events), equal-cumulative guard only when a finite nonnegative total is actually reported, and physical record position as identity when cumulative is absent. The weakened cross-file replay protection without cumulative is a documented, reasoned trade-off. Cache bumps (v16 + missing-cumulative-v1 parse version) follow the house convention so stale caches reprice.
iamtoruk
left a comment
There was a problem hiding this comment.
Ran this against my own corpus: 1,399 rollouts, 136,145 token_count events. The shape this fixes (info present, total_token_usage missing) shows up 0 times. All 685 no-cumulative events are info: null, one per session, the opening rate-limit ping, and those already take the estimate path at codex.ts:1108.
Checked your corpus too. Spread-sampled 30 sessions from codeset-release-evals: 1,308 events, 30 null-info, 0 of the target shape. The "54 of 1313" figure is those null-info pings. The re-emission half of your data holds up (624 of 1308 byte-identical repeats), but that half is already collapsed by the cumulative guard.
Built both sides. audit --provider codex --period lifetime is byte-identical: 51,399 calls, $5,226.17. Delta is $0.00.
Tests and tsc are green, rebase onto main is clean. So this is harmless, but the cache bump makes every user cold-reparse their whole Codex history to get the same numbers back. Drop CODEX_CACHE_VERSION back to 15 and drop the -missing-cumulative-v1 suffix.
Also fix the Quirks line in docs/providers/codex.md. It says those 54 events key on record position. They do not, they are null-info and go to the estimate path.
One thing worth noting: the codex:record:: fallback throws away the forkedFromId identity #1383 just added for sub-agent parents. Nothing hits it today, but if the missing-cumulative shape ever appears in a sub-agent rollout it double counts the replayed parent history.
Fix the docs and the version bumps and I will take it.
…per review Re-verified on a fresh spread sample of codeset-release-evals (21 sessions, 404 events): exactly one info:null rate-limit ping per session, zero events with info present but total_token_usage absent — matching the reviewer's 137k-event corpus. The missing-cumulative branch is a no-op on real data: - CODEX_CACHE_VERSION stays 15 (no cold reparse for identical numbers; v16 never shipped in a release) - drop the -missing-cumulative-v1 parse suffix - Quirks: null-info pings take the estimate path, not the dedup path; the earlier 54-of-1313 figure conflated them with partial-info events - dedup layer 3 now documents the forkedFromId/getagentseal#1383 sub-agent caveat
|
Your corpus wins, and my "54" was a conflation — re-verified before conceding. Fresh spread sample of codeset-release-evals (21 sessions, 404 events): exactly 21 a7fae30 applies all three asks:
Locally: the three named suites pass (65/65), |
|
Merged. Thanks for re-verifying the spread sample and conceding the count instead of arguing it, that is exactly how these should go. The docs now say what the code does and the cache version stays put, so nobody gets a cold reparse for identical numbers. |
Closing the loop on the merge — thank you both. This thread ended up mattering beyond the patch: the re-review against a 136k-event corpus caught a conflation in my own analysis (null-info rate-limit pings vs partial-info events), which is now corrected in our published rollout-topology reference and analysis script, and recorded as a methodology rule — classify before normalizing, and let a second corpus check any sampled claim. The three-layer dedup as merged (byte-identity collapse with the measured 45-46% re-emission rate, equal-cumulative guard, record-position fallback with the #1383 caveat documented) is the shape we'll cite elsewhere.Repro fixtures: AgentMeasure conformance pack — plain JSONL + expected totals, no runtime install. Found during an open ecosystem audit of usage-accounting tools. |
… total+last) (#250) * fix(codex): collapse byte-identical token_count re-emissions Codex re-emits the same token_count event (identical total AND last usage) multiple times within one response — 603 of 1,313 events across a 53-session public rollout corpus (codeset-ai/codeset-release-evals). The prefer-last delta path counted each repeat as a fresh turn, inflating per-response usage by the re-emission factor (~46%). Guard: skip an event whose last_token_usage triple is identical to the previous event's, mirroring the collapse codeburn adopted after real- data review (getagentseal/codeburn#1264, merged). Fixture reemitted-session.jsonl + test pin the behavior. Note: I couldn't run cargo locally (no toolchain on this machine); CI is the referee for the new test. * fix(parser): key re-emission collapse on (total, last); move tests into mod tests; cover the undercount direction Review follow-up on the re-emission guard: - identity is now the six-tuple (total, last) as written in the original description: a repeat of last with an advancing total is a distinct turn and must survive — the last-only key dropped it (invisible undercount, worse than the overcount being fixed) - the two tests move into the existing mod tests (fixture_path scope), fixing the compile error; formatting follows rustfmt line breaks - new fixture repeated-last-advancing-total.jsonl pins the undercount direction; reemitted-session.jsonl still pins the collapse direction - point-in-time corpus numbers move out of the source comment into the PR description (the why stays) * chore(parser): apply rustfmt and compact the advancing-total fixture CI's `cargo fmt --all -- --check` rejected the re-emission key tuple on all three OSes. The advancing-total fixture also used spaced JSON unlike every other Codex fixture; it now matches the compact form. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(parser): keep the re-emission key across events without last usage `prev_key = key` ran on every non-duplicate event, so a token_count without `last_token_usage` (key = None) wiped the stored identity. A byte-identical re-emission arriving after it was then counted as a second turn, which is the over-count this PR set out to remove. That event shape is real: the PR's own reemitted-session fixture carries one. Only overwrite the key when the event has one. The skip branch keeps assigning `prev_totals` so it always holds the latest observed total, the same invariant every other path keeps. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(parser): scope the re-emission key to one session The dedup key survived a mid-file session_meta, so a new session whose first turn happened to match the previous session's last six-tuple would be collapsed as a re-emission. Reset it where provider is already reset. `prev_totals` is deliberately left as is. It predates this guard and feeds the total-diff fallback; whether Codex restarts totals at a mid-file session_meta is not pinned by any test. Across 1,223 local rollouts every extra session_meta sits at line 0-1, before any token_count, so neither choice changes real-data results today. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * test(parser): count the new Codex fixtures in collect_files This PR adds four Codex fixtures, so the exact-count assertion moves from 27 to 31. It went unnoticed because CI stopped at the rustfmt step. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * test(services): give the Codex Fast tier-switch turns cumulative totals test_codex_fast_log_to_cost (from #255) emitted both turns with the same total and last usage. Totals are cumulative, so no real second turn looks like that, and the re-emission guard now collapses it into the first. Advancing the second turn's total keeps what the test checks (per-tier pricing of `last`) and leaves the expected costs unchanged. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * test(parser): assert both turns in full across a session boundary The session-boundary test only checked the second turn's input and output. Check every counted field on both turns so a regression in either one fails. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: mag123c <diehreo@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
When
total_token_usageis absent, the old cumulative guard and all-zero deduplication key collapse subsequent requests. Keying only on last usage also loses distinct requests with equal token amounts.This revision:
total_tokens;Validation: 37 tests pass across the relevant Codex test files, including the new three-case regression and cache invalidation coverage; TypeScript
--noEmitpasses. The three missing-cumulative regressions fail on the previous PR head and pass with this revision. Fixtures are synthetic.