Repository navigation
fix(codex): count unreported compaction usage - #1821
Conversation
Match token_usage_record responses to compacted markers and add usage omitted from Codex's cumulative token counts. Suppress records already covered by an advancing snapshot so local compaction stays counted once. Deduplicate response IDs across copied sessions, including parents outside the report's date range, and preserve deterministic attribution and speed tier merging in serial and parallel aggregation. Rework the contribution from #1805 for the current adapter. Co-authored-by: Henry Teo <[email protected]>
Allow the line classifier to fall back to its whitespace-aware scan for turn contexts and nested token counts with spaces after type colons. This prevents valid JSONL from silently omitting normal request usage when calculating totals alongside compaction records.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | abe086f | Commit Preview URL Branch Preview URL |
Oct 02 2026, 09:34 PM |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughCodex now parses automatic compaction usage from session logs, filters compactions replayed from parent sessions, and deduplicates copied compaction events during report aggregation. Documentation and regression tests cover matching, attribution, replay, and cumulative usage handling. ChangesCodex compaction accounting
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant CodexSessionParser
participant CodexReplayPlan
participant CodexSessionLoader
participant CodexAggregator
CodexSessionParser->>CodexReplayPlan: Parent compaction IDs and timestamps
CodexSessionLoader->>CodexReplayPlan: Check child path and response ID
CodexReplayPlan-->>CodexSessionLoader: Whether compaction is replayed
CodexSessionLoader->>CodexAggregator: Retained usage events
CodexAggregator->>CodexAggregator: Deduplicate compactions by response ID
Merge Risk: ⚪ Minimal · up to No actionable merge-blocking risk is identified for the Codex compaction accounting change; it is mergeable after normal checks. Architecture SummaryArchitecture risk: 🔵 Low · up to The change affects 2 systems. Changed systems: Architecture concerns Review detailsSystems and components
Before / after behavior
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 55.88% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 68 functions across 8 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai @cubic-dev-ai Please review this replacement for #1805, especially suppression of compaction usage already included in cumulative token counts, fork replay when the parent falls outside the date range, and deterministic serial/parallel attribution. Claude CLI has also reviewed the implementation; its date-bounded replay finding is fixed and covered by a regression test. |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-x64
commit: |
There was a problem hiding this comment.
All reported issues were addressed across 10 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
There was a problem hiding this comment.
ℹ️ Minor suggestions only — one edge case worth a look.
Reviewed changes
Reviewed the full diff of this run (5a41412 compaction counting plus ad2d201 whitespace parsing):
- Compaction pairing in
parser.rs— pairstoken_usage_recordentries withcompactedmarkers by response ID in either order, and suppresses the extra event when an intervening cumulativetoken_countsnapshot already accounts for the usage (exact-delta match orthread_token_usagematch). - Response-ID dedupe across aggregation paths — new
CodexEventKey::Response/CodexEventDeduplicationKey::Responsekeys inaggregate.rsandloader.rscollapse copied compaction responses to one, with first-file-wins attribution, merged service tiers, and deferred accumulation so parallel workers agree. - Fork-replay handling in
replay.rs— compaction IDs are indexed per parent and excluded from the normal replay prefix, so date-bounded child reports drop parent copies without losing the child's own requests. - Whitespace-tolerant line classification — compact byte finders plus a whitespace-aware
typescanner so spaced JSON no longer silently dropstoken_count,token_usage_record, orcompactedlines. - Tests and docs — exact-total assertions that fail under double counting (e.g.
510in the local-compaction test), serial/parallel parity checks, and updated Codex guide plus adapter README.
The tests assert exact token totals rather than loose bounds, so they genuinely pin the fix. Parities I verified by reading: loader dedupe keeps first-file attribution matching aggregate's file_index rule, aggregate_events merges tiers the same way, and compaction_tiers[response_id] indexing is safe because empty-model events continue before both the seeding pass and the use site.
muse-spark-1.3-contributor (free via Pullfrog for OSS) | 𝕏
Keep matched parent response IDs independently of usage emission, so a retained child log missing the intervening cumulative snapshot cannot count an already-accounted compaction again in a date-bounded report. Collect identities during the existing parent pass. Normal report parsing does not allocate the optional history map, and ordinary replay matching continues to use only token-count deltas. Add a regression covering the loader and serial/parallel reports with a missing child snapshot. This addresses Cubic's review finding on #1821.
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
|
@coderabbitai @cubic-dev-ai Please review dbac9e6, which retains matched parent compaction IDs even when cumulative snapshots already cover their usage. The missing-child-snapshot regression now passes through the loader and serial/parallel bounded reports, and Claude CLI found no correctness bugs in this follow-up. |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
There was a problem hiding this comment.
All reported issues were addressed across 10 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --stat dbac9e6b
git show --format=fuller --no-ext-diff dbac9e6b -- rust/adapters/codex/src/parser.rs rust/adapters/codex/src/replay.rs rust/adapters/codex/src/compaction_tests.rs
ast-grep outline rust/adapters/codex/src/parser.rs --items all
ast-grep outline rust/adapters/codex/src/replay.rs --items all
ast-grep outline rust/adapters/codex/src/compaction_tests.rs --items allLength of output: 19699 🏁 Script executed: #!/bin/bash
set -euo pipefail
git rev-parse HEAD
nl -ba rust/adapters/codex/src/parser.rs | sed -n '201,557p'
nl -ba rust/adapters/codex/src/replay.rs | sed -n '75,202p'
nl -ba rust/adapters/codex/src/replay.rs | sed -n '218,272p;309,430p'
nl -ba rust/adapters/codex/src/compaction_tests.rs | sed -n '1,178p'
ast-grep outline rust/adapters/codex/src/loader.rs --items all
ast-grep outline rust/adapters/codex/src/aggregate.rs --items all
rg -n -C 5 'is_replayed_compaction\(|for_bounded_files\(|replay_prefix\(' rust/adapters/codex/src/loader.rs rust/adapters/codex/src/aggregate.rs
git show dbac9e6b -- rust/adapters/codex/src/README.mdLength of output: 53414
The loader and both aggregation paths apply the same compaction replay filter. This change does not alter attribution or service-tier merging. This was a static code review. I did not run the tests. You are interacting with an AI system. |
There was a problem hiding this comment.
ℹ️ No new issues in this delta — one prior thread stays open.
Reviewed changes
Reviewed the delta since the prior pullfrog review (ad2d201 → dbac9e6):
- Retained counted compaction IDs in replay history — parent scans now collect every matched
token_usage_record/compactedresponse ID (including snapshot-covered usage) via the newvisit_codex_session_file_with_compaction_historypath, so date-bounded child reports suppress the copy even when the child log omits the cumulative snapshot. - Kept the normal replay prefix compaction-free —
read_usage_eventsexcludesresponse_idevents from the prefix timestamps/usage, preserving the existing token-count matching behavior while the ID map handles compactions independently. - Added a missing-snapshot regression test —
bounded_reports_exclude_counted_parent_compactions_without_a_copied_snapshotpins the exact120-token total across serial, parallel, and loader paths. - Updated the adapter README — documents that all matched parent compaction IDs are preserved, including snapshot-covered usage.
The new test asserts exact totals (not loose bounds), so it genuinely pins the fix. Parities I verified by reading: normal report parsing passes None for the history map (no allocation, no behavior change), or_insert_with keeps first-seen timestamps, and the end-of-file retain drops unmatched IDs before extending the parent map. The prior inline thread on latest_usage_response_id being cleared before validation (parser.rs:364) is unchanged by this delta and stays open for the author to decide.
muse-spark-1.3-contributor (free via Pullfrog for OSS) | 𝕏
Keep the latest valid response ID when an invalid or already-accounted record is ignored. Clearing it before validation prevented cumulative snapshots from consuming the pending compaction usage and counted it twice. Add exact-total regressions for missing IDs, timestamps, empty usage, and duplicate completed responses. This addresses Pullfrog's review on #1821.
|
@coderabbitai @cubic-dev-ai Please review the latest commits, 8f4df2a and abe086f. Invalid or completed records preserve the latest valid pending usage ID, and the documentation now distinguishes first-copy attribution from merged service tiers. All 125 Codex tests and workspace push checks passed. The previous CI test job failed because the Nix SQLite database was busy, so the latest CI run also retries that environment failure. |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git rev-parse HEAD
git show --stat 8f4df2a0
git show --stat abe086fc
git show --format=fuller --no-ext-diff 8f4df2a0 abe086fc
ast-grep outline rust/adapters/codex/src/parser.rs --items all
ast-grep outline rust/adapters/codex/src/aggregate.rs --items all
ast-grep outline rust/adapters/codex/src/compaction_tests.rs --items allLength of output: 17499 🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba rust/adapters/codex/src/parser.rs | sed -n '68,97p;201,556p;1283,1317p'
nl -ba rust/adapters/codex/src/compaction_tests.rs | sed -n '179,375p'
nl -ba rust/adapters/codex/src/aggregate.rs | sed -n '578,777p;839,892p;918,990p'
nl -ba rust/adapters/codex/src/README.md | sed -n '1,45p'Length of output: 46594
This was a static review. I did not run tests or verify the latest CI result. Thanks for clarifying the attribution rules. ✏️ Learnings added
You are interacting with an AI system. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the delta since the prior pullfrog review (dbac9e6 → abe086f), which addresses the open latest_usage_response_id thread:
- Preserved valid pending usage across invalid records — removed the pre-validation reset so
latest_usage_response_idis only overwritten after timestamp and usage validation, letting the cumulative snapshot still suppress the pending compaction entry. - Added exact-total regression coverage — new
invalid_or_completed_records_do_not_hide_pending_compaction_usagetest pins all four gap shapes (blank ID, missing timestamp, empty usage, duplicate completed response) at450total tokens with noresponse_idevents leaking. - Clarified duplicate compaction attribution docs — README now states first-file session, timestamp, and model retention with merged service tiers, matching the
insert_dedupe_recordfirst-file-wins behavior.
The new test asserts exact totals rather than loose bounds, so it genuinely pins the fix. Parities I verified by reading: early returns for blank IDs, missing timestamps, zero usage, and already-emitted IDs all exit before the latest_usage_response_id assignment at parser.rs:428, and the duplicate-record path exits via the emitted_response_ids guard.
muse-spark-1.3-contributor (free via Pullfrog for OSS) | 𝕏
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
|
Confirmed this omission in the currently published ccusage 20.0.26 on macOS arm64 (checked npm latest on 2026-10-06). Posting the reproduction here because this PR already fixes the source, and release PR #1809 is still open. The v20.0.26 session parser accepts turn_context / event_msg token_count but skips token_usage_record and compacted. This matches missing remote-compaction requests in an independently reconciled local history. Synthetic reproduction: save these four lines as sessions/synthetic.jsonl under an isolated Codex home: {"timestamp":"2026-10-05T00:00:00Z","type":"turn_context","payload":{"model":"gpt-6-astra"}}
{"timestamp":"2026-10-05T00:00:01Z","type":"event_msg","payload":{"type":"token_count","info":{"total_token_usage":{"input_tokens":1000,"cached_input_tokens":900,"output_tokens":20,"reasoning_output_tokens":0,"total_tokens":1020},"last_token_usage":{"input_tokens":1000,"cached_input_tokens":900,"output_tokens":20,"reasoning_output_tokens":0,"total_tokens":1020}}}}
{"timestamp":"2026-10-05T00:00:02Z","type":"token_usage_record","payload":{"response_id":"synthetic-compaction-1","model":"gpt-6-astra","usage":{"input_tokens":3000,"cached_input_tokens":2000,"output_tokens":30,"reasoning_output_tokens":0,"total_tokens":3030}}}
{"timestamp":"2026-10-05T00:00:03Z","type":"compacted","payload":{"compaction_response_id":"synthetic-compaction-1"}}Run with CODEX_HOME pointing to that isolated directory: ccusage codex daily --json --offline --speed standard --timezone UTC --since 2026-10-05 --until 2026-10-05I ran this against the 20.0.26 native binary. It exited successfully but returned:
Expected cost uses standard GPT-6 Astra input/cache/output rates of $10/$1/$50 per million. All example records are synthetic; no private logs or identifiers are included. I verified the released binary's omission, not a build of this merged fix. A published release containing #1821 should address this specific gap. Thanks for fixing it. |

Codex remote compaction can persist provider usage without adding it to the cumulative
token_counttotals, causing ccusage reports to omit those requests. Pairtoken_usage_recordandcompactedentries by response ID, and add only usage that an intervening cumulative snapshot has not already counted. The distinction follows Codex's remote compaction path and local compaction path.Copied compaction responses are counted once, including when a fork's parent is outside the selected date range. Parent history retains matched response IDs even when cumulative snapshots already account for their usage, protecting retained child logs with a missing snapshot. Serial and parallel aggregation retain the same session, timestamp, model attribution, and merged speed tier. A separate follow-up commit fixes whitespace-sensitive JSON classification exposed by the CLI regression checks.
Supersedes #1805, based on @HenryTeo27's contribution. Henry Teo is credited as a co-author of the implementation commit. The documentation explains the local-log coverage and inferred model attribution.
Validation:
-D warnings, formatting, and secret checks passed through the push hooks.just typecheckandjust testpassed.Summary by CodeRabbit
Bug Fixes
Documentation