fix(amp): fall back to messages[].usage when usageLedger is absent#1145
Conversation
The Rust Amp parser only emitted entries when usageLedger.events was present, so current Amp installs that store usage directly on each assistant message (model, timestamp, token fields inside messages[].usage) reported "No Amp usage data found." Now we read those usage objects when the ledger is missing. Closes #1138
📝 WalkthroughWalkthroughThe Amp parser now prefers ChangesAmp usage parser fallback to messages array
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 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 |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | f52ff3b | Commit Preview URL Branch Preview URL |
May 24 2026, 11:23 PM |
There was a problem hiding this comment.
Pull request overview
Fixes Amp usage parsing in the Rust ccusage adapter by supporting the current Amp thread schema where token usage is stored on messages[].usage when usageLedger.events is absent, while keeping the legacy ledger path as the preferred source when available (closes #1138).
Changes:
- Parse
usageLedger.events[]when present (existing behavior), including cache breakdown viamessages[].usagekeyed bytoMessageId. - Add a fallback path to emit entries from assistant
messages[].usagewhen the ledger/events array is missing. - Add tests covering the new messages-based schema, precedence of ledger events, and skipping zero-token usage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| rust/crates/ccusage/src/adapter/amp/parser.rs | Adds messages-based usage parsing fallback and supporting tests while preserving ledger precedence. |
| rust/crates/ccusage/src/adapter/amp/README.md | Updates Amp adapter docs to describe the ledger-vs-messages usage sources. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - `messages[].usage` directly when `usageLedger.events` is not present (current | ||
| Amp schema). Each assistant message's `usage` object carries `model`, | ||
| `timestamp`, and the token fields below. |
There was a problem hiding this comment.
Updated the README in f52ff3b. The token-field list now spells out which keys belong to each schema variant: legacy usageLedger.events[].tokens uses input/output/total, and the current messages[].usage schema uses inputTokens/outputTokens/cacheCreationInputTokens/cacheReadInputTokens/totalTokens, with totalTokens documented as a fallback for the split fields.
Generated by Claude Code
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
rust/crates/ccusage/src/adapter/amp/parser.rs (1)
275-401: ⚡ Quick winMove new parser cases to fixture-backed tests.
These cases are good, but the inline JSON blobs make schema-variant maintenance harder. Please move them to fixtures and keep tests focused on assertions.
As per coding guidelines,
**/*.rs: "prefer fixture-backed parser/loader tests".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/crates/ccusage/src/adapter/amp/parser.rs` around lines 275 - 401, Move the inline JSON blobs in tests reads_usage_from_messages_when_ledger_is_missing, ledger_events_take_precedence_over_messages_usage, and skips_messages_with_no_usage_tokens into dedicated fixture files and update the tests to load those fixtures instead of embedding the strings; specifically, create fixture files containing each JSON payload and change the tests to call read_thread_file on the fixture path (reusing the existing read_thread_file function) and keep the existing assertions unchanged so the parser behavior is verified against file-backed inputs rather than inline literals.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@rust/crates/ccusage/src/adapter/amp/parser.rs`:
- Around line 167-179: The zero-token filter currently checks TokenUsageRaw
fields (input_tokens, output_tokens, cache_creation_input_tokens,
cache_read_input_tokens) and continues before applying fallbacks from aggregate
fields like totalTokens/totalInputTokens, causing valid AMP records to be
dropped; update the parsing in parser.rs (around TokenUsageRaw construction
where json_value_u64 is used) to first populate/merge aggregate token fields
into the detailed fields (e.g., use totalTokens or totalInputTokens to set
input_tokens/output_tokens when the detailed fields are missing or zero), then
perform the zero-token check, and apply the same change to the analogous block
around the later code (lines ~199-208) so both branches handle aggregates before
skipping.
---
Nitpick comments:
In `@rust/crates/ccusage/src/adapter/amp/parser.rs`:
- Around line 275-401: Move the inline JSON blobs in tests
reads_usage_from_messages_when_ledger_is_missing,
ledger_events_take_precedence_over_messages_usage, and
skips_messages_with_no_usage_tokens into dedicated fixture files and update the
tests to load those fixtures instead of embedding the strings; specifically,
create fixture files containing each JSON payload and change the tests to call
read_thread_file on the fixture path (reusing the existing read_thread_file
function) and keep the existing assertions unchanged so the parser behavior is
verified against file-backed inputs rather than inline literals.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6666019a-732d-4735-9566-1fb8b1c86424
📒 Files selected for processing (2)
rust/crates/ccusage/src/adapter/amp/README.mdrust/crates/ccusage/src/adapter/amp/parser.rs
There was a problem hiding this comment.
1 issue found across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-arm64
@ccusage/ccusage-win32-x64
commit: |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
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 runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
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. |
Mirrors the legacy ledger-event behavior so a `messages[].usage` object that reports only `totalTokens` (without the split input/output/cache fields) still yields a usage entry instead of being filtered out by the zero-token check. Also clarifies the README to spell out the field names each schema variant uses and that `totalTokens` is only a fallback.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
rust/crates/ccusage/src/adapter/amp/parser.rs (1)
174-176:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSupport
totalInputTokensfallback in the messages path too.The fallback only reads
usage.totalTokens. Amp data can also provide aggregate usage astotalInputTokens; in that case this path still gets filtered as zero usage.Suggested patch
- let total_tokens = json_value_u64(usage.get("totalTokens")); + let total_tokens = json_value_u64(usage.get("totalTokens")) + .max(json_value_u64(usage.get("totalInputTokens"))); let (usage_raw, extra_total_tokens) = apply_total_token_fallback(usage_raw, 0, total_tokens);+ #[test] + fn falls_back_to_total_input_tokens_in_messages_path() { + let nanos = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let dir = env::temp_dir().join(format!("ccusage-amp-messages-total-input-{nanos}")); + fs::create_dir_all(&dir).unwrap(); + let file = dir.join("thread.json"); + fs::write( + &file, + r#"{ + "id":"T-thread-a", + "messages":[ + {"role":"assistant","usage":{ + "model":"claude-haiku-4-5-20251001", + "totalInputTokens":345, + "timestamp":"2026-01-19T11:42:10.652Z" + }} + ] + }"#, + ) + .unwrap(); + + let entries = read_thread_file(&file, None, CostMode::Auto, None).unwrap(); + fs::remove_dir_all(&dir).unwrap(); + + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].data.message.usage.output_tokens, 345); + assert_eq!(entries[0].extra_total_tokens, 0); + }As per coding guidelines, "Add Rust fixture-backed tests for path discovery, parser behavior, aggregation totals, and important legacy compatibility."
Also applies to: 181-181, 421-451
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/crates/ccusage/src/adapter/amp/parser.rs` around lines 174 - 176, The messages-path fallback is only reading usage.totalTokens so aggregate counts provided as totalInputTokens are ignored; update the parser code in parser.rs where total_tokens is computed (currently via json_value_u64(usage.get("totalTokens"))) to also check usage.get("totalInputTokens") and prefer that value when present (e.g., try totalInputTokens then totalTokens) before calling apply_total_token_fallback(usage_raw, 0, total_tokens); ensure the same change is applied at the other occurrences noted (around lines 181 and 421-451) and add Rust fixture-backed tests that cover path discovery, parser behavior, aggregation totals and legacy compatibility for totalInputTokens vs totalTokens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@rust/crates/ccusage/src/adapter/amp/parser.rs`:
- Around line 174-176: The messages-path fallback is only reading
usage.totalTokens so aggregate counts provided as totalInputTokens are ignored;
update the parser code in parser.rs where total_tokens is computed (currently
via json_value_u64(usage.get("totalTokens"))) to also check
usage.get("totalInputTokens") and prefer that value when present (e.g., try
totalInputTokens then totalTokens) before calling
apply_total_token_fallback(usage_raw, 0, total_tokens); ensure the same change
is applied at the other occurrences noted (around lines 181 and 421-451) and add
Rust fixture-backed tests that cover path discovery, parser behavior,
aggregation totals and legacy compatibility for totalInputTokens vs totalTokens.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e2d5aba2-416f-4508-88e0-df0c7722d054
📒 Files selected for processing (2)
rust/crates/ccusage/src/adapter/amp/README.mdrust/crates/ccusage/src/adapter/amp/parser.rs
✅ Files skipped from review due to trivial changes (1)
- rust/crates/ccusage/src/adapter/amp/README.md
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
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 runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
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. |
Summary
usageLedger.eventswas present, so current Amp installs that store usage directly on each assistant message reportedNo Amp usage data found.usageLedger.eventsis missing, the parser reads each assistant message'susageobject (model,timestamp,inputTokens,outputTokens,cacheCreationInputTokens,cacheReadInputTokens).usageLedger.eventsdata still takes precedence when present; the messages-only fallback only kicks in when the ledger is absent.Closes #1138
Test plan
cargo test -p ccusage adapter::amp— 4 tests pass (legacy fallback, new schema, precedence, empty-tokens skip).cargo test -p ccusage— 215 tests pass, no regressions.cargo clippy -p ccusage --testsclean.~/.local/share/amp/threads(per the issue, 94/121 files use the new schema).Generated by Claude Code
Summary by cubic
Fixes Amp usage parsing in
ccusageby readingmessages[].usagewhenusageLedger.eventsis missing and falling back tototalTokenswhen split fields are absent, so newer Amp installs report usage correctly. Legacy ledger data still takes precedence. Closes #1138.model,timestamp, and token fields frommessages[].usagewhen no ledger is present.totalTokensas a fallback in the messages path; still skip zero-token entries.usageLedger.eventsas the source of truth when available.Written for commit f52ff3b. Summary will update on new commits. Review in cubic
Summary by CodeRabbit
Documentation
Bug Fixes