feat(codex): read saved exec JSON usage#1036
Conversation
Add CODEX_EXEC_LOG_DIR so ccusage can include JSONL files captured from codex exec --json alongside normal CODEX_HOME session logs. The Codex parser now accepts direct and nested usage payloads, keeps model and timestamp metadata when present, and falls back to the file timestamp for saved output lines that do not include one.
|
@cubic-dev-ai review it |
|
@coderabbitai review it |
📝 WalkthroughWalkthroughThis PR extends Codex usage tracking to ingest saved ChangesCodex headless JSONL usage ingestion via CODEX_HOME paths
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 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 | a182bc8 | Commit Preview URL Branch Preview URL |
May 19 2026, 11:42 AM |
@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: Length of output: 19524 Let me read the full parser file to check for potential double-counting and model extraction issues. 🏁 Script executed: Length of output: 28773
OverviewThe feature cleanly adds 🐛 Bug:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@apps/ccusage/src/adapter/codex/parser.ts`:
- Around line 186-197: extractModelFromResult currently calls extractModel only
on nested objects but extractModel does not inspect top-level model_name, so
records like {data: {model_name, ...}} or {result: {model_name, ...}} are
missed; update extractModelFromResult (or alter extractModel) to explicitly
check for model_name fields on payload and its nested
payload.data/payload.result/payload.response (e.g., payload.model_name,
payload.data.model_name, payload.result.model_name) and return that value before
falling back to calling extractModel, ensuring per-model aggregation picks up
headless result records.
🪄 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: 13f8e690-bafb-435c-ad6f-18ee0a22985f
📒 Files selected for processing (6)
apps/ccusage/src/adapter/codex/parser.tsapps/ccusage/src/adapter/codex/paths.tsdocs/guide/codex/index.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.md
Teach the shared Codex model extractor to read model_name at the root of direct and nested exec JSON payloads. This prevents saved codex exec usage rows from falling back to the legacy model when model_name is the only model field, and narrows JSONL markers to key-colon matches.
|
@coderabbitai review it |
|
@cubic-dev-ai review it |
|
@coderabbitai Addressed in e417520 by adding root/nested model_name extraction, covering the single-entry nested model_name case with a regression test, and narrowing the JSONL markers to key-colon matches. Validation: pnpm run format, pnpm --filter ccusage exec vitest run src/adapter/codex/paths.ts src/adapter/codex/parser.ts src/adapter/codex/index.ts, pnpm typecheck, pnpm run test, git diff --check. |
@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: Length of output: 14937 🏁 Script executed: Length of output: 2987
The minor ordering note about If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 52088
✅
const fallbackModel = asNonEmptyString(payload.model) ?? asNonEmptyString(payload.model_name);The ✅
✅ JSONL markers – Significantly improved The markers are now The PR looks solid overall — path utilities are clean, deduplication handles cross-directory overlap, docs are thorough, and the tests cover the new paths well. LGTM 🐇 ✨ [approve] |
Replace the inline agent label conditional in the schema generator with a small exhaustive switch helper. This keeps future agent-specific labels out of nested ternaries and makes the config schema label rules easier to extend.
|
@coderabbitai review it |
|
@cubic-dev-ai review it |
@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: Length of output: 28433
Everything looks good. LGTM 🐇 ✨ [approve] |
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 comparisonThis compares the PR build against the base branch build on the same CI runner. 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
Package size
Lower medians and smaller packed package sizes are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
Merge the current Rust-based main branch into the Codex exec JSON PR and keep the public configuration model aligned with Claude-style data paths. Saved codex exec --json output is now discovered through comma-separated CODEX_HOME entries. Entries that contain sessions/ are treated as normal Codex homes, while entries without sessions/ are read directly as JSONL directories, so a command such as CODEX_HOME="/Users/ryoppippi/.codex,/Users/ryoppippi/codex-exec-logs" covers both sources without adding a separate JSON option or CODEX_EXEC_LOG_DIR. The Rust Codex loader now handles standalone exec usage payloads, nested result usage/model fields, stringified token counts, and missing timestamps by falling back to the source file modification time. Documentation was updated to describe only CODEX_HOME-based discovery.
|
@coderabbitai review Updated this PR to merge current main and keep saved Codex exec JSON usage support in the Rust CLI. Per review feedback, this now uses comma-separated CODEX_HOME entries only: Codex homes with sessions/ are scanned normally, and entries without sessions/ are read directly as saved codex exec --json JSONL directories. Local validation passed:
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 28289
✅ The two-function split is clean. ✅ The ✅ The ✅ Handles stringified token counts emitted by some Codex backends. ✅ Test coverage — Solid
✅ Docs — Consistent All No issues. LGTM 🐇 ✨ [approve] |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
rust/crates/ccusage/src/adapter/codex.rs (1)
112-117:⚠️ Potential issue | 🟠 Major | ⚡ Quick winScope dedupe to a single usage root.
This loop reuses one
seenset across allcodex_usage_paths(), so two unrelated roots can suppress each other when they emit the same timestamp/model/token tuple. That undercounts multi-root configs. The simplest fix is to create a fresh dedupe set per path unless you also add a stable source identifier to the dedupe key.Suggested change
fn load_groups(shared: &SharedArgs, kind: AgentReportKind) -> Result<BTreeMap<String, CodexGroup>> { let mut groups = BTreeMap::new(); - let seen = create_dedupe_shards(); for path in crate::codex_usage_paths()? { + let seen = create_dedupe_shards(); merge_groups( &mut groups, load_groups_from_directory_with_dedupe(&path, shared, kind, &seen)?, ); } Ok(groups) }🤖 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/codex.rs` around lines 112 - 117, The dedupe set `seen` is currently shared across all roots in the loop over crate::codex_usage_paths(), causing entries from different usage roots to suppress each other; to fix, scope dedupe per root by creating a fresh `seen` (or equivalent HashSet) for each `path` before calling load_groups_from_directory_with_dedupe(&path, shared, kind, &seen) and pass that fresh set into load_groups_from_directory_with_dedupe so deduplication is limited to that usage root (alternatively, include a stable source identifier in the dedupe key inside load_groups_from_directory_with_dedupe if cross-root dedupe is desired).
🤖 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.
Outside diff comments:
In `@rust/crates/ccusage/src/adapter/codex.rs`:
- Around line 112-117: The dedupe set `seen` is currently shared across all
roots in the loop over crate::codex_usage_paths(), causing entries from
different usage roots to suppress each other; to fix, scope dedupe per root by
creating a fresh `seen` (or equivalent HashSet) for each `path` before calling
load_groups_from_directory_with_dedupe(&path, shared, kind, &seen) and pass that
fresh set into load_groups_from_directory_with_dedupe so deduplication is
limited to that usage root (alternatively, include a stable source identifier in
the dedupe key inside load_groups_from_directory_with_dedupe if cross-root
dedupe is desired).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 638c0aba-23ce-47c7-99ba-599c27b3e725
📒 Files selected for processing (6)
docs/guide/codex/index.mddocs/guide/environment-variables.mddocs/guide/getting-started.mdrust/crates/ccusage/src/adapter/codex.rsrust/crates/ccusage/src/codex_loader.rsrust/crates/ccusage/src/main.rs
✅ Files skipped from review due to trivial changes (4)
- rust/crates/ccusage/src/main.rs
- docs/guide/environment-variables.md
- docs/guide/getting-started.md
- docs/guide/codex/index.md
| let mut previous_totals: Option<CodexRawUsage> = None; | ||
| let mut current_model: Option<String> = None; | ||
| let mut current_model_is_fallback = false; | ||
| let fallback_timestamp = file_modified_timestamp(path); |
There was a problem hiding this comment.
Use a per-record fallback timestamp here.
Every timestamp-less exec row in the file inherits the same fallback_timestamp, so two legitimate rows with the same model/token counts collapse in dedupe_codex_events and usage gets undercounted. Please derive the fallback per line/event, or mark synthesized timestamps so dedupe does not treat them as identical.
Also applies to: 250-287
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. |
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. |
Try This PR
Summary
Validation
Summary by CodeRabbit
Release Notes
New Features
CODEX_HOMElocations, alongside session directories.Documentation
CODEX_HOMEfor direct JSONL log directories.