Skip to content

feat(codex): include archived sessions - #1230

Merged
ryoppippi merged 3 commits into
mainfrom
codex/archived-sessions
Jun 8, 2026
Merged

ryoppippi merged 3 commits into
mainfrom
codex/archived-sessions

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jun 8, 2026 •

Copy link
Copy Markdown
Member

Loads Codex JSONL files from archived_sessions next to each CODEX_HOME sessions directory so archived Codex usage remains included in reports.

The direct JSONL directory fallback is preserved for saved codex exec --json outputs, and the Codex guide now documents the archived source.

Testing:

  • direnv exec . cargo test -p ccusage adapter::codex
  • direnv exec . cargo fmt -p ccusage
  • direnv exec . pnpm run format
  • direnv exec . cargo run -q -p ccusage --bin ccusage -- codex daily --json --offline
  • pre-push hooks: clippy, treefmt, gitleaks, cargo test

Summary by cubic

Include archived Codex sessions in usage reports by reading JSONL from archived_sessions alongside sessions. Keeps the direct JSONL fallback and avoids duplicate reads when CODEX_HOME entries repeat.

  • New Features
    • Discover sessions/ and archived_sessions/ independently for each CODEX_HOME.
    • Fall back to the home path as a direct JSONL directory only when neither directory exists.
    • Clarify docs and add tests for sessions-only, archived-only, fallback, and deduped CODEX_HOME entries.

Written for commit b01fd43. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Codex scanning now recognizes both active sessions and archived session directories and includes them when present; if neither exists, the entry itself is used as a fallback.
  • Documentation

    • Updated guidance to clarify how Codex homes are discovered and how archived-session directories are handled.

Load archived_sessions next to a Codex home sessions directory so archived Codex JSONL files remain visible in focused and unified reports.

Keep direct JSONL directory CODEX_HOME entries working by only falling back to the home path when neither sessions nor archived_sessions exists.

Document the additional Codex source and cover path discovery with a fixture-backed unit test.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai please review this PR. It adds archived Codex session discovery and keeps the existing direct JSONL directory fallback intact.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94cf702a-4059-4d6a-9ccc-2c550660a493

📥 Commits

Reviewing files that changed from the base of the PR and between 0373b5f and b01fd43.

📒 Files selected for processing (2)
  • docs/guide/codex/index.md
  • rust/crates/ccusage/src/adapter/codex/paths.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rust/crates/ccusage/src/adapter/codex/paths.rs

📝 Walkthrough

Walkthrough

This PR extends the Codex session discovery logic to recognize archived_sessions/ directories in addition to sessions/ directories under each Codex home path. The implementation adds a helper function with deduplication, includes unit tests verifying both directories and fallback behavior, and updates README and user-facing documentation.

Changes

Codex archived_sessions support

Layer / File(s) Summary
Archived sessions discovery implementation and testing
rust/crates/ccusage/src/adapter/codex/paths.rs, rust/crates/ccusage/src/adapter/codex/README.md, docs/guide/codex/index.md
codex_usage_paths() delegates to a new helper that discovers and deduplicates both sessions/ and archived_sessions/ subdirectories under each home, with a fallback to the home path itself when neither exists. Unit tests cover both-directory, single-directory, fallback, and deduplication cases. The README and user guide are updated to document the scanning behavior.
sequenceDiagram
  participant CLI
  participant codex_usage_paths_from_homes
  participant FS
  CLI->>codex_usage_paths_from_homes: call with CODEX_HOME entries
  codex_usage_paths_from_homes->>FS: check "<home>/sessions/" existence
  codex_usage_paths_from_homes->>FS: check "<home>/archived_sessions/" existence
  codex_usage_paths_from_homes->>CLI: return deduplicated list of usage paths
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • ryoppippi/ccusage#1036: Overlaps on Codex path discovery changes and usage-source selection logic.
  • ryoppippi/ccusage#1131: Touches Codex adapter internals around home discovery and session file loading; related to codex_usage_paths behavior.

Suggested labels

enhancement

Suggested reviewers

  • ubugeeei

Poem

🐰 The Codex grows with time, my friend,
Sessions old must somewhere end—
Archived paths now join the chase,
Deduped, discovered, find their place.
A tiny hop for logs, a tidy trace. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(codex): include archived sessions' directly and clearly describes the main change—adding support for archived sessions discovery in the Codex data source integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/archived-sessions

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide b01fd43 Commit Preview URL

Branch Preview URL
Jun 08 2026, 12:37 AM

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details and diff
gh pr view 1230 --repo ryoppippi/ccusage --json title,body,files,additions,deletions,headRefName

Length of output: 2457


🏁 Script executed:

#!/bin/bash
# Get the diff for the PR
gh pr diff 1230 --repo ryoppippi/ccusage

Length of output: 4041


🏁 Script executed:

#!/bin/bash
# Read the codex adapter file
cat rust/crates/ccusage/src/adapter/codex.rs

Length of output: 402


🏁 Script executed:

#!/bin/bash
# Read the codex loader file
cat rust/crates/ccusage/src/codex_loader.rs

Length of output: 399


🏁 Script executed:

#!/bin/bash
# Check for any tests related to archived sessions
rg -n "archived" --type rust

Length of output: 988


🏁 Script executed:

#!/bin/bash
# Check the AGENTS.md file in adapter directory
cat rust/crates/ccusage/src/adapter/AGENTS.md

Length of output: 2303


🏁 Script executed:

#!/bin/bash
# Check openclaw paths.rs for test patterns comparison
cat rust/crates/ccusage/src/adapter/openclaw/paths.rs

Length of output: 2774


🏁 Script executed:

#!/bin/bash
# Check the full codex paths.rs for context
cat rust/crates/ccusage/src/adapter/codex/paths.rs

Length of output: 2191


🏁 Script executed:

#!/bin/bash
# Check ccusage_test_support Fixture API
fd -t f "lib.rs" --search-path rust/crates/ccusage-test-support/
fd -t f "*.rs" --search-path rust/crates/ccusage-test-support/

Length of output: 751


🏁 Script executed:

#!/bin/bash
cat rust/crates/ccusage-test-support/src/lib.rs

Length of output: 2457



@ryoppippi: The implementation is clean and the logic is correct. Here's my review:


Review

✅ Logic

The found_usage_dir flag correctly prevents the direct-path fallback when either sessions/ or archived_sessions/ exists. All four cases are handled properly:

sessions/ archived_sessions/ result
✓ ✓ both pushed, no fallback
✓ ✗ sessions only, no fallback
✗ ✓ archived only, no fallback
✗ ✗ direct-path fallback

The seen deduplication is preserved for both sub-paths, ordering is deterministic (sessions/ before archived_sessions/), and the refactoring of the public function into a testable codex_usage_paths_from_homes helper is a nice improvement.

⚠️ Test coverage gap

Per AGENTS.md ("Add Rust fixture-backed tests for path discovery … and important legacy compatibility"), the three edge cases below aren't covered yet:

#[test]
fn sessions_only_excludes_archived_path() {
    let fixture = Fixture::new();
    let _ = fixture.create_dir_all("codex/sessions");
    // no archived_sessions dir

    let paths = codex_usage_paths_from_homes(vec![fixture.path("codex")]);

    assert_eq!(paths, vec![fixture.path("codex/sessions")]);
}

#[test]
fn archived_sessions_only_no_direct_fallback() {
    let fixture = Fixture::new();
    let _ = fixture.create_dir_all("codex/archived_sessions");
    // no sessions dir

    let paths = codex_usage_paths_from_homes(vec![fixture.path("codex")]);

    assert_eq!(paths, vec![fixture.path("codex/archived_sessions")]);
}

#[test]
fn falls_back_to_direct_path_when_neither_subdir_exists() {
    let fixture = Fixture::new();
    let home = fixture.create_dir_all("codex"); // no sessions/, no archived_sessions/

    let paths = codex_usage_paths_from_homes(vec![home.clone()]);

    assert_eq!(paths, vec![home]);
}

The archived_sessions_only_no_direct_fallback case is especially worth pinning — it's the non-obvious behavior change where found_usage_dir = true suppresses the fallback even when sessions/ is absent.

✅ Docs

Both docs/guide/codex/index.md and rust/crates/ccusage/src/adapter/codex/README.md are accurate and clear. No issues there.


🧠 Learnings used
Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T16:02:17.988Z
Learning: In `ryoppippi/ccusage`, `visit_codex_session_file` in `rust/crates/ccusage/src/codex_loader.rs` uses bounded-memory streaming via `BufReader::with_capacity(128 * 1024, file)` + `read_until(b'\n', &mut line)` with `line.clear()` each iteration (restored in commit ebf035f). The `fs::read` / `byte_lines` (full-file-in-memory) approach was deliberately reverted to avoid memory spikes on large JSONL files.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T15:24:46.102Z
Learning: In `ryoppippi/ccusage`, the `load_groups` function in `rust/crates/ccusage/src/adapter/codex.rs` has a single-CODEX_HOME fast path guarded by `paths.len() == 1 && !wants_json(shared)`. The worker-local aggregation fast path is intentionally restricted to human-table output only; JSON/jq output always uses the global-dedupe aggregation path to avoid cross-chunk fallback cost after all chunks are processed.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Add Rust fixture-backed tests for path discovery, parser behavior, aggregation totals, and important legacy compatibility.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/rust/crates/ccusage/src/**/*.rs : Add or update CLI JSON assertions and table snapshots for affected report modes.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:48:37.050Z
Learning: In `ryoppippi/ccusage`, session timestamps in `rust/crates/ccusage/src/codex_loader.rs` use a dedicated `codex_session_timestamp` helper (introduced in commit 9b83895) that fast-paths string timestamps (returned trimmed as-is, no normalization overhead) and only delegates numeric timestamps to `normalize_codex_timestamp`. This was added to avoid the normalization cost on the common string-timestamp case in the hot session parsing path.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:48:37.050Z
Learning: In `ryoppippi/ccusage`, the `codex_line_usage_kind` classifier in `rust/crates/ccusage/src/codex_loader.rs` uses compact JSON key-value finders (`"type":"turn_context"`, `"type":"event_msg"`, `"type":"token_count"`) rather than bare substring finders. A line is classified as a Codex session row only when `"type":"turn_context"` is present, or when both `"type":"event_msg"` AND `"type":"token_count"` appear together. This was tightened in commit 9b83895 to prevent headless rows with `token_count` in arbitrary content fields from being misrouted to the session parser.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T17:26:33.070Z
Learning: In `ryoppippi/ccusage`, malformed headless Codex rows in `rust/crates/ccusage/src/codex_loader.rs` are handled by a two-tier fallback (added in commit 695a46e): first attempt typed `CodexLogEntry<'_>` deserialization; on failure, fall back to `add_codex_exec_event_from_value` which parses via `serde_json::Value` and uses `_value` helpers (`normalize_value_timestamp`, `codex_model_from_result_value`, `normalize_headless_codex_usage_value`) that mirror the typed path. The `usage_from_value` helper uses `serde_json::from_value(v.clone()).ok()` to reuse the `CodexRawUsage` deserializer including the `total_tokens: 0` guard.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Use shared file walking, JSONL scanning where applicable, SQLite loading, logging, pricing fetcher lifecycle, date formatting, table rendering, and all-agent aggregation utilities across adapters.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Add skipped local-data smoke tests when real user log directories are useful for catching schema drift.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T17:26:33.070Z
Learning: In `ryoppippi/ccusage`, `load_groups_from_directory` in `rust/crates/ccusage/src/adapter/codex.rs` no longer has a worker-local parallel aggregation path. As of commit 695a46e, the parallel branch always uses `create_dedupe_shards()` + `aggregate_files_parallel` (global dedupe in one pass). The previous `aggregate_files_parallel_local` function (with its cross-chunk reread fallback) was removed entirely.
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Add fixture-backed coverage for sessions-only, archived-sessions-only, and direct JSONL directory fallback path discovery.

This addresses the CodeRabbit review gap while preserving the archived session discovery behavior added in the prior commit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@docs/guide/codex/index.md`:
- Line 26: Update the paragraph to reflect the actual discovery logic from
paths.rs: state that both sessions/ and archived_sessions/ are discovered
independently (an entry with only archived_sessions/ adds that directory rather
than the parent), that archived_sessions/ is not contingent on the presence of
sessions/, and that the JSONL-directory fallback (reading the entry directly as
a JSONL directory) only happens when neither sessions/ nor archived_sessions/
exist; reference CODEX_HOME and the directories sessions/, archived_sessions/,
and the JSONL fallback so readers can map this to the implementation.

In `@rust/crates/ccusage/src/adapter/codex/paths.rs`:
- Around line 55-70: Add four new fixture-backed tests for
codex_usage_paths_from_homes: (1)
falls_back_to_home_when_neither_sessions_nor_archived_exists — create only
"codex" and assert result == vec![fixture.path("codex")]; (2)
includes_only_sessions_when_archived_does_not_exist — create "codex/sessions"
and assert result == vec![fixture.path("codex/sessions")]; (3)
includes_only_archived_when_sessions_does_not_exist — create
"codex/archived_sessions" and assert result ==
vec![fixture.path("codex/archived_sessions")]; and (4)
deduplicates_across_multiple_homes — create "codex/sessions", call
codex_usage_paths_from_homes with vec![path.clone(), path.clone()] and assert
result == vec![fixture.path("codex/sessions")]; use the existing Fixture::new()
and Fixture.create_dir_all calls and the same assertion style as the existing
includes_archived_sessions_next_to_sessions test to validate fallback and
seen-HashSet deduplication.
🪄 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: 455ca464-b984-4c5f-89f4-4a851e6611b4

📥 Commits

Reviewing files that changed from the base of the PR and between 9786ab1 and 28e2e29.

📒 Files selected for processing (3)
  • docs/guide/codex/index.md
  • rust/crates/ccusage/src/adapter/codex/README.md
  • rust/crates/ccusage/src/adapter/codex/paths.rs

Comment thread docs/guide/codex/index.md Resolved
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai addressed your test coverage gap by adding fixture-backed cases for sessions-only, archived-sessions-only without direct fallback, and direct JSONL directory fallback. Validation passed: direnv exec . cargo test -p ccusage adapter::codex and pre-push hooks including clippy and cargo test.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ryoppippi: All three edge cases are now covered — sessions-only, archived-sessions-only (the non-obvious found_usage_dir suppression), and the direct-path fallback. The implementation looks solid and the test suite is in great shape. Thanks for closing the loop!

🐇🎉


🧠 Learnings used
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Add Rust fixture-backed tests for path discovery, parser behavior, aggregation totals, and important legacy compatibility.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T15:24:46.102Z
Learning: In `ryoppippi/ccusage`, the `load_groups` function in `rust/crates/ccusage/src/adapter/codex.rs` has a single-CODEX_HOME fast path guarded by `paths.len() == 1 && !wants_json(shared)`. The worker-local aggregation fast path is intentionally restricted to human-table output only; JSON/jq output always uses the global-dedupe aggregation path to avoid cross-chunk fallback cost after all chunks are processed.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T17:26:33.070Z
Learning: In `ryoppippi/ccusage`, `load_groups_from_directory` in `rust/crates/ccusage/src/adapter/codex.rs` no longer has a worker-local parallel aggregation path. As of commit 695a46e, the parallel branch always uses `create_dedupe_shards()` + `aggregate_files_parallel` (global dedupe in one pass). The previous `aggregate_files_parallel_local` function (with its cross-chunk reread fallback) was removed entirely.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T16:02:17.988Z
Learning: In `ryoppippi/ccusage`, `visit_codex_session_file` in `rust/crates/ccusage/src/codex_loader.rs` uses bounded-memory streaming via `BufReader::with_capacity(128 * 1024, file)` + `read_until(b'\n', &mut line)` with `line.clear()` each iteration (restored in commit ebf035f). The `fs::read` / `byte_lines` (full-file-in-memory) approach was deliberately reverted to avoid memory spikes on large JSONL files.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:31:40.598Z
Learning: In `ryoppippi/ccusage`, the worker-local aggregation fallback in `aggregate_files_parallel_local` (rust/crates/ccusage/src/adapter/codex.rs) is intentionally left without observability instrumentation. The local-dedupe fast path is optimized for the common single-`CODEX_HOME` case where cross-chunk duplicates are rare; the fallback re-runs the full global-dedupe pass to preserve correctness without adding per-event retained state to the hot path. This is a deliberate design trade-off by the maintainer.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:48:37.050Z
Learning: In `ryoppippi/ccusage`, the `codex_line_usage_kind` classifier in `rust/crates/ccusage/src/codex_loader.rs` uses compact JSON key-value finders (`"type":"turn_context"`, `"type":"event_msg"`, `"type":"token_count"`) rather than bare substring finders. A line is classified as a Codex session row only when `"type":"turn_context"` is present, or when both `"type":"event_msg"` AND `"type":"token_count"` appear together. This was tightened in commit 9b83895 to prevent headless rows with `token_count` in arbitrary content fields from being misrouted to the session parser.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 1062
File: rust/crates/ccusage/src/claude_loader.rs:919-929
Timestamp: 2026-05-19T13:00:16.948Z
Learning: In `rust/crates/ccusage/src/claude_loader.rs`, `extract_project()` returns only the first path segment after `projects/`. As a result, composite/non-segment project filters (e.g. "project-a/session-a") intentionally yield empty results because `entry.project` (a single segment) will never match a multi-segment filter string. The `usage_files` function's full-discovery fallback for non-segment filters preserves this pre-existing behavior to maintain parity with bunx ccusage. Do not suggest bypassing the exact-match filter for non-segment inputs, as that would break parity.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T17:26:33.070Z
Learning: In `ryoppippi/ccusage`, malformed headless Codex rows in `rust/crates/ccusage/src/codex_loader.rs` are handled by a two-tier fallback (added in commit 695a46e): first attempt typed `CodexLogEntry<'_>` deserialization; on failure, fall back to `add_codex_exec_event_from_value` which parses via `serde_json::Value` and uses `_value` helpers (`normalize_value_timestamp`, `codex_model_from_result_value`, `normalize_headless_codex_usage_value`) that mirror the typed path. The `usage_from_value` helper uses `serde_json::from_value(v.clone()).ok()` to reuse the `CodexRawUsage` deserializer including the `total_tokens: 0` guard.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/rust/crates/ccusage/src/**/*.rs : Add or update CLI JSON assertions and table snapshots for affected report modes.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Add skipped local-data smoke tests when real user log directories are useful for catching schema drift.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:41.349Z
Learning: Use `testing` skill for Rust cargo tests, snapshots, fixtures, Claude models, LiteLLM pricing tests, and Vitest tests

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:35.145Z
Learning: Use the `testing` skill for Rust cargo tests, CLI snapshots, Claude model names, LiteLLM pricing tests, Vitest tests, and filesystem fixtures

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-17T11:50:36.709Z
Learning: In the `ryoppippi/ccusage` repository, the correct test fixture dependency for simulated agent data directories is `fs-fixture` (declared as `"fs-fixture": "catalog:testing"` in `apps/ccusage/package.json`). Tests use `createFixture()` from `fs-fixture`. There is no `ccusage-testing` package or fixture factory in this repo — do not suggest switching to one.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Keep adapter code responsible for source paths, raw parsing, token mapping, model mapping, source metadata, and agent-specific pricing.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:48:37.050Z
Learning: In `ryoppippi/ccusage`, session timestamps in `rust/crates/ccusage/src/codex_loader.rs` use a dedicated `codex_session_timestamp` helper (introduced in commit 9b83895) that fast-paths string timestamps (returned trimmed as-is, no normalization overhead) and only delegates numeric timestamps to `normalize_codex_timestamp`. This was added to avoid the normalization cost on the common string-timestamp case in the hot session parsing path.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — adds archived_sessions/ discovery to the Codex path scanner so archived session JSONL files are included in usage reports.

  • Add archived_sessions/ to codex_usage_paths — the path scanner now checks for archived_sessions/ alongside sessions/ under each CODEX_HOME directory. The direct JSONL fallback (for saved codex exec --json output) now only triggers when neither sessions/ nor archived_sessions/ exists. Extracted codex_usage_paths_from_homes as a pure helper for testability.
  • Document the archived source — updated the Codex guide's Data Source section and the adapter README.md.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/guide/codex/index.md Resolved
@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ccusage@1230

@ccusage/ccusage-darwin-arm64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-arm64@1230

@ccusage/ccusage-darwin-x64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-x64@1230

@ccusage/ccusage-linux-arm64

npx https://pkg.pr.new/@ccusage/ccusage-linux-arm64@1230

@ccusage/ccusage-linux-x64

npx https://pkg.pr.new/@ccusage/ccusage-linux-x64@1230

@ccusage/ccusage-win32-arm64

npx https://pkg.pr.new/@ccusage/ccusage-win32-arm64@1230

@ccusage/ccusage-win32-x64

npx https://pkg.pr.new/@ccusage/ccusage-win32-x64@1230

commit: b01fd43

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 28e2e2917bcc
Base SHA: 9786ab1afbc0

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 9786ab1afbc0 1.242s 573.5ms 29.7ms 3
PR pkg.pr.new 28e2e29 783.6ms 579.2ms 28.6ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 9786ab1afbc0; PR package: 28e2e29. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 536.7ms 536.5ms 1.00x 311.08 MiB 325.08 MiB 1.05x 1.88 GiB/s 1.88 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 360.1ms 354.6ms 1.02x 76.58 MiB 80.70 MiB 1.05x 2.80 GiB/s 2.84 GiB/s

Package runtime diagnostics

Compares 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 538.5ms 1.87 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 508.6ms 1.98 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 359.0ms 2.80 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 327.8ms 3.07 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 27.1ms 27.8ms 0.97x 43.48 MiB 43.73 MiB 1.01x 0.06 MiB/s 0.06 MiB/s
claude session --offline --json 0.00 MiB 27.5ms 27.5ms 1.00x 43.48 MiB 43.61 MiB 1.00x 0.06 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 27.9ms 27.1ms 1.03x 43.61 MiB 43.48 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 27.7ms 27.3ms 1.01x 43.48 MiB 43.61 MiB 1.00x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 528.3ms 531.6ms 0.99x 304.95 MiB 333.70 MiB 1.09x 1.91 GiB/s 1.89 GiB/s
codex --offline --json 1.01 GiB 353.5ms 348.3ms 1.01x 76.83 MiB 69.83 MiB 0.91x 2.85 GiB/s 2.89 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.35 KiB 14.35 KiB -0.00 KiB 1.00x
installed native package binary 3289.62 KiB 3289.62 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 28e2e2917bcc
Base SHA: 9786ab1afbc0

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 9786ab1afbc0 817.3ms 557.2ms 33.9ms 3
PR pkg.pr.new 28e2e29 975.5ms 682.2ms 31.6ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 9786ab1afbc0; PR package: 28e2e29. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 550.1ms 543.2ms 1.01x 306.95 MiB 294.20 MiB 0.96x 1.83 GiB/s 1.85 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 366.8ms 370.7ms 0.99x 73.83 MiB 71.20 MiB 0.96x 2.75 GiB/s 2.72 GiB/s

Package runtime diagnostics

Compares 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 551.4ms 1.83 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 505.9ms 1.99 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 362.5ms 2.78 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 340.6ms 2.96 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.8ms 4.0ms 7.38x 43.73 MiB 2.70 MiB 0.06x 0.05 MiB/s 0.38 MiB/s
claude session --offline --json 0.00 MiB 30.0ms 4.0ms 7.49x 43.61 MiB 2.70 MiB 0.06x 0.05 MiB/s 0.39 MiB/s
codex daily --offline --json 0.00 MiB 29.3ms 3.7ms 7.86x 43.48 MiB 2.70 MiB 0.06x 0.03 MiB/s 0.23 MiB/s
codex session --offline --json 0.00 MiB 29.6ms 3.8ms 7.84x 43.48 MiB 2.70 MiB 0.06x 0.03 MiB/s 0.23 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 547.9ms 529.5ms 1.03x - 309.83 MiB - 1.84 GiB/s 1.90 GiB/s
codex --offline --json 1.01 GiB 375.1ms 334.5ms 1.12x 80.58 MiB 80.20 MiB 1.00x 2.68 GiB/s 3.01 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.35 KiB 14.35 KiB -0.00 KiB 1.00x
installed native package binary 3289.62 KiB 3289.62 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 0373b5fa17cc
Base SHA: 9786ab1afbc0

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 9786ab1afbc0 967.4ms 629.6ms 29.6ms 3
PR pkg.pr.new 0373b5f 825.7ms 676.8ms 30.2ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 9786ab1afbc0; PR package: 0373b5f. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 532.9ms 532.8ms 1.00x 296.83 MiB 317.45 MiB 1.07x 1.89 GiB/s 1.89 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 360.1ms 360.0ms 1.00x 76.83 MiB 79.45 MiB 1.03x 2.80 GiB/s 2.80 GiB/s

Package runtime diagnostics

Compares 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 526.5ms 1.91 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 506.1ms 1.99 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 355.3ms 2.83 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 335.0ms 3.01 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 27.9ms 3.7ms 7.48x 43.61 MiB 2.70 MiB 0.06x 0.06 MiB/s 0.42 MiB/s
claude session --offline --json 0.00 MiB 27.4ms 3.8ms 7.29x 43.61 MiB 2.70 MiB 0.06x 0.06 MiB/s 0.41 MiB/s
codex daily --offline --json 0.00 MiB 27.6ms 3.6ms 7.76x 43.61 MiB 2.70 MiB 0.06x 0.03 MiB/s 0.24 MiB/s
codex session --offline --json 0.00 MiB 27.0ms 3.6ms 7.59x 43.61 MiB 2.70 MiB 0.06x 0.03 MiB/s 0.24 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 523.1ms 500.5ms 1.05x 306.58 MiB 293.95 MiB 0.96x 1.92 GiB/s 2.01 GiB/s
codex --offline --json 1.01 GiB 358.1ms 324.6ms 1.10x 78.70 MiB 78.70 MiB 1.00x 2.81 GiB/s 3.10 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.35 KiB 14.35 KiB -0.00 KiB 1.00x
installed native package binary 3289.62 KiB 3289.62 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 0373b5fa17cc
Base SHA: 9786ab1afbc0

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 9786ab1afbc0 541.3ms 656.6ms 32.4ms 3
PR pkg.pr.new 0373b5f 805.0ms 850.0ms 32.1ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 9786ab1afbc0; PR package: 0373b5f. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 548.2ms 555.3ms 0.99x 297.45 MiB 326.20 MiB 1.10x 1.84 GiB/s 1.81 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 377.3ms 371.5ms 1.02x 80.33 MiB 81.70 MiB 1.02x 2.67 GiB/s 2.71 GiB/s

Package runtime diagnostics

Compares 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 555.1ms 1.81 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 510.4ms 1.97 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 359.7ms 2.80 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 335.7ms 3.00 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 30.3ms 30.5ms 0.99x 43.61 MiB 43.48 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 29.7ms 30.1ms 0.99x 43.61 MiB 43.48 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 29.9ms 30.0ms 1.00x 43.73 MiB 43.48 MiB 0.99x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 29.9ms 29.9ms 1.00x 43.48 MiB 43.73 MiB 1.01x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 551.3ms 537.9ms 1.02x 319.08 MiB 294.08 MiB 0.92x 1.83 GiB/s 1.87 GiB/s
codex --offline --json 1.01 GiB 370.2ms 367.6ms 1.01x 81.83 MiB 82.83 MiB 1.01x 2.72 GiB/s 2.74 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.35 KiB 14.35 KiB -0.00 KiB 1.00x
installed native package binary 3289.62 KiB 3289.62 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

Clarify Codex home discovery documentation so sessions, archived_sessions, and the direct JSONL fallback match the implementation.

Add fixture-backed coverage for duplicate CODEX_HOME entries to pin the seen-set behavior requested in review.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai please review the latest push b01fd43. It addresses the docs discovery wording and adds the duplicate CODEX_HOME path dedupe test requested in your review.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the specific commit diff
gh api repos/ryoppippi/ccusage/commits/b01fd43b --jq '.files[] | {filename: .filename, status: .status, additions: .additions, deletions: .deletions, patch: .patch}'

Length of output: 2130


🏁 Script executed:

#!/bin/bash
# Also get the current state of paths.rs and docs
cat rust/crates/ccusage/src/adapter/codex/paths.rs

Length of output: 3523


🏁 Script executed:

#!/bin/bash
# Check the docs file
cat docs/guide/codex/index.md

Length of output: 6841



@ryoppippi: Both changes look great — this is a clean push.

✅ Docs wording (docs/guide/codex/index.md)

The old phrasing ("matching archived_sessions/ directories are read automatically") implied archived_sessions/ was subordinate to sessions/ being present. The new sentence — "ccusage discovers sessions/ and archived_sessions/ independently, so an entry with only archived_sessions/ still contributes archived Codex logs" — correctly describes the actual behavior and removes the ambiguity that could have misled users with an archived-only home.

✅ New test (deduplicates_usage_paths_across_repeated_homes)

The test correctly exercises the cross-home seen deduplication path:

let paths = codex_usage_paths_from_homes(vec![home.clone(), home]);
assert_eq!(paths, vec![fixture.path("codex/sessions")]);

Passing the same home twice mirrors the real-world scenario where a user accidentally duplicates an entry in CODEX_HOME. The FxHashSet in codex_usage_paths_from_homes correctly blocks the second insertion of the same sessions/ path.

Summary of full test coverage

Test Scenario
includes_archived_sessions_next_to_sessions both subdirs present
uses_sessions_without_missing_archived_sessions_path sessions-only
uses_archived_sessions_without_direct_path_fallback archived-only, no fallback
falls_back_to_direct_path_when_no_session_directories_exist neither subdir → direct fallback
deduplicates_usage_paths_across_repeated_homes (new) repeated CODEX_HOME entry deduped

All five cases are fixture-backed. This PR is ready to merge. 🎉

🐇 ✨

[approve]


🧠 Learnings used
Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T15:24:46.102Z
Learning: In `ryoppippi/ccusage`, the `load_groups` function in `rust/crates/ccusage/src/adapter/codex.rs` has a single-CODEX_HOME fast path guarded by `paths.len() == 1 && !wants_json(shared)`. The worker-local aggregation fast path is intentionally restricted to human-table output only; JSON/jq output always uses the global-dedupe aggregation path to avoid cross-chunk fallback cost after all chunks are processed.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Add Rust fixture-backed tests for path discovery, parser behavior, aggregation totals, and important legacy compatibility.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:31:40.598Z
Learning: In `ryoppippi/ccusage`, the worker-local aggregation fallback in `aggregate_files_parallel_local` (rust/crates/ccusage/src/adapter/codex.rs) is intentionally left without observability instrumentation. The local-dedupe fast path is optimized for the common single-`CODEX_HOME` case where cross-chunk duplicates are rare; the fallback re-runs the full global-dedupe pass to preserve correctness without adding per-event retained state to the hot path. This is a deliberate design trade-off by the maintainer.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T17:26:33.070Z
Learning: In `ryoppippi/ccusage`, `load_groups_from_directory` in `rust/crates/ccusage/src/adapter/codex.rs` no longer has a worker-local parallel aggregation path. As of commit 695a46e, the parallel branch always uses `create_dedupe_shards()` + `aggregate_files_parallel` (global dedupe in one pass). The previous `aggregate_files_parallel_local` function (with its cross-chunk reread fallback) was removed entirely.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:48:37.050Z
Learning: In `ryoppippi/ccusage`, the `codex_line_usage_kind` classifier in `rust/crates/ccusage/src/codex_loader.rs` uses compact JSON key-value finders (`"type":"turn_context"`, `"type":"event_msg"`, `"type":"token_count"`) rather than bare substring finders. A line is classified as a Codex session row only when `"type":"turn_context"` is present, or when both `"type":"event_msg"` AND `"type":"token_count"` appear together. This was tightened in commit 9b83895 to prevent headless rows with `token_count` in arbitrary content fields from being misrouted to the session parser.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 1062
File: rust/crates/ccusage/src/claude_loader.rs:919-929
Timestamp: 2026-05-19T13:00:16.948Z
Learning: In `rust/crates/ccusage/src/claude_loader.rs`, `extract_project()` returns only the first path segment after `projects/`. As a result, composite/non-segment project filters (e.g. "project-a/session-a") intentionally yield empty results because `entry.project` (a single segment) will never match a multi-segment filter string. The `usage_files` function's full-discovery fallback for non-segment filters preserves this pre-existing behavior to maintain parity with bunx ccusage. Do not suggest bypassing the exact-match filter for non-segment inputs, as that would break parity.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 1040
File: apps/ccusage/src/commands/index.ts:175-207
Timestamp: 2026-05-17T18:49:01.449Z
Learning: In ryoppippi/ccusage, the top-level agent commands in `apps/ccusage/src/commands/index.ts` (claude, codex, opencode, amp, pi) are intentionally wired to their respective daily leaf runners (e.g., `codexDailyCommand`, `opencodeDailyCommand`) as the parent command's handler. Invoking `ccusage <agent>` without a report subcommand deliberately defaults to showing the daily report — this preserves the pre-PR behavior of `normalizeAgentCommandArgs` (which mapped `agent [--flags]` → `agent:daily [--flags]`). Do not flag the absence of an `ctx.omitted` guard or a dedicated parent-only command in these entries; the default-daily behavior is intentional and covered by cli-output.test.ts.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T16:02:17.988Z
Learning: In `ryoppippi/ccusage`, `visit_codex_session_file` in `rust/crates/ccusage/src/codex_loader.rs` uses bounded-memory streaming via `BufReader::with_capacity(128 * 1024, file)` + `read_until(b'\n', &mut line)` with `line.clear()` each iteration (restored in commit ebf035f). The `fs::read` / `byte_lines` (full-file-in-memory) approach was deliberately reverted to avoid memory spikes on large JSONL files.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T14:48:37.050Z
Learning: In `ryoppippi/ccusage`, session timestamps in `rust/crates/ccusage/src/codex_loader.rs` use a dedicated `codex_session_timestamp` helper (introduced in commit 9b83895) that fast-paths string timestamps (returned trimmed as-is, no normalization overhead) and only delegates numeric timestamps to `normalize_codex_timestamp`. This was added to avoid the normalization cost on the common string-timestamp case in the hot session parsing path.

Learnt from: ryoppippi
Repo: ryoppippi/ccusage PR: 0
File: :0-0
Timestamp: 2026-05-22T17:26:33.070Z
Learning: In `ryoppippi/ccusage`, malformed headless Codex rows in `rust/crates/ccusage/src/codex_loader.rs` are handled by a two-tier fallback (added in commit 695a46e): first attempt typed `CodexLogEntry<'_>` deserialization; on failure, fall back to `add_codex_exec_event_from_value` which parses via `serde_json::Value` and uses `_value` helpers (`normalize_value_timestamp`, `codex_model_from_result_value`, `normalize_headless_codex_usage_value`) that mirror the typed path. The `usage_from_value` helper uses `serde_json::from_value(v.clone()).ok()` to reuse the `CodexRawUsage` deserializer including the `total_tokens: 0` guard.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/rust/crates/ccusage/src/**/*.rs : Add or update CLI JSON assertions and table snapshots for affected report modes.

Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: rust/crates/ccusage/src/adapter/AGENTS.md:0-0
Timestamp: 2026-05-23T23:21:58.299Z
Learning: Applies to rust/crates/ccusage/src/adapter/**/*.rs : Add skipped local-data smoke tests when real user log directories are useful for catching schema drift.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — refined the Codex guide paragraph to describe independent sessions//archived_sessions/ discovery and added a deduplication test for repeated CODEX_HOME entries.

  • Refined the Codex Data Source paragraph in docs/guide/codex/index.md — the new wording describes sessions/ and archived_sessions/ discovery as independent so an entry with only archived_sessions/ clearly contributes logs.
  • Added deduplicates_usage_paths_across_repeated_homes test in paths.rs — verifies the FxHashSet deduplication prevents double-counting when the same CODEX_HOME directory appears multiple times.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: b01fd43b18bf
Base SHA: 9786ab1afbc0

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 9786ab1afbc0 597.1ms 629.0ms 30.7ms 3
PR pkg.pr.new b01fd43 589.3ms 710.2ms 31.4ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 9786ab1afbc0; PR package: b01fd43. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 556.7ms 550.8ms 1.01x 307.83 MiB 298.33 MiB 0.97x 1.81 GiB/s 1.83 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 363.1ms 366.1ms 0.99x 78.58 MiB 73.83 MiB 0.94x 2.77 GiB/s 2.75 GiB/s

Package runtime diagnostics

Compares 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 547.9ms 1.84 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 510.2ms 1.97 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 362.8ms 2.77 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 340.8ms 2.95 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.5ms 29.3ms 1.01x 43.61 MiB 43.61 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 29.2ms 29.0ms 1.01x 43.61 MiB 43.48 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 28.6ms 29.1ms 0.98x 43.61 MiB 43.48 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 29.3ms 28.1ms 1.04x 43.73 MiB 43.48 MiB 0.99x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 541.8ms 530.7ms 1.02x 311.58 MiB 319.95 MiB 1.03x 1.86 GiB/s 1.90 GiB/s
codex --offline --json 1.01 GiB 361.6ms 354.5ms 1.02x 74.20 MiB 78.83 MiB 1.06x 2.78 GiB/s 2.84 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.35 KiB 14.35 KiB -0.00 KiB 1.00x
installed native package binary 3289.62 KiB 3289.62 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: b01fd43b18bf
Base SHA: 9786ab1afbc0

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 9786ab1afbc0 534.1ms 539.9ms 32.7ms 3
PR pkg.pr.new b01fd43 629.3ms 2.090s 33.1ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 9786ab1afbc0; PR package: b01fd43. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 559.3ms 559.4ms 1.00x 311.83 MiB 307.20 MiB 0.99x 1.80 GiB/s 1.80 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 370.4ms 369.9ms 1.00x 70.70 MiB 78.83 MiB 1.11x 2.72 GiB/s 2.72 GiB/s

Package runtime diagnostics

Compares 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 546.0ms 1.84 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 521.0ms 1.93 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 361.9ms 2.78 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 331.8ms 3.03 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 30.3ms 4.2ms 7.28x 43.61 MiB 2.70 MiB 0.06x 0.05 MiB/s 0.37 MiB/s
claude session --offline --json 0.00 MiB 29.6ms 4.0ms 7.50x 43.48 MiB 2.70 MiB 0.06x 0.05 MiB/s 0.39 MiB/s
codex daily --offline --json 0.00 MiB 29.5ms 3.7ms 7.88x 43.48 MiB 2.70 MiB 0.06x 0.03 MiB/s 0.23 MiB/s
codex session --offline --json 0.00 MiB 29.1ms 3.7ms 7.77x 43.48 MiB 2.70 MiB 0.06x 0.03 MiB/s 0.23 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 540.6ms 512.7ms 1.05x 332.08 MiB 300.45 MiB 0.90x 1.86 GiB/s 1.96 GiB/s
codex --offline --json 1.01 GiB 385.0ms 349.8ms 1.10x 76.83 MiB 82.58 MiB 1.07x 2.61 GiB/s 2.88 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.35 KiB 14.35 KiB -0.00 KiB 1.00x
installed native package binary 3289.62 KiB 3289.62 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@ryoppippi
ryoppippi merged commit a9b12c4 into main Jun 8, 2026
40 checks passed
@ryoppippi
ryoppippi deleted the codex/archived-sessions branch June 8, 2026 10:19
ryoppippi pushed a commit to yashau/ccusage that referenced this pull request Jun 8, 2026
Rebase the remaining ccusage#1176 behavior on top of the archived_sessions support that landed in ccusage#1230.

Collect Codex JSONL files through scoped usage sources so an active sessions/ file wins over an archived_sessions/ file with the same relative path within the same CODEX_HOME. Keep different CODEX_HOME roots independent, and use PathBuf keys instead of lossy string conversion for relative paths.
ryoppippi pushed a commit that referenced this pull request Jun 8, 2026
Keep active sessions/ JSONL files when archived_sessions/ has the same relative path within one CODEX_HOME, while preserving separate CODEX_HOME roots and non-lossy PathBuf relative keys.\n\nRebased and scoped from #1176 after #1230 landed the basic archived_sessions discovery.\n\nCo-authored-by: yashau <[email protected]>
ryoppippi added a commit that referenced this pull request Nov 10, 2026
The generated Codex fixture is mostly zstd-compressed rollouts, some in
`archived_sessions/`. Releases before #1890 skip `.jsonl.zst` files
(and before #1230 skip archived sessions), so a history run across
that change compared an old ref reading a fraction of the input with a
new ref reading all of it, and reported the difference as a CPU and
RSS regression.

The generator gains `--codex-layout legacy`, which writes the same
rollouts uncompressed under `sessions/`. bench-history keeps the
current layout when every ref is a commit containing #1890, and
otherwise generates the legacy layout and says so at the top of the
report; binary paths count as unknown. With a custom Codex fixture
that holds `.zst` files and a ref that cannot read them, and for
`--real` runs across the change, the report leads with a warning
instead. The PR comparison always builds refs that read the current
layout, so CI keeps compressed coverage.

Reported by CodeRabbit on #2061.
ryoppippi added a commit that referenced this pull request Nov 10, 2026
The compatibility check only looked for `.jsonl.zst` files, but
releases before #1230 also skip `archived_sessions/`, so a custom
fixture with archived plain rollouts got no warning and a history run
across #1230 compared different input.

Replace the single zstd commit with a table of Codex input classes taken
from the Codex loader's history (`paths.rs`, `reader.rs`): archived
sessions (a9b12c4, #1230, v20.0.7) and zstd-compressed rollouts
(44a3aa8, #1890, v20.0.33). Each ref is checked per class by git
ancestry; the generated fixture switches to the legacy layout when any
ref skips any class, and a custom fixture or `--real` run warns per
class with the refs that skip it. Claude discovery has scanned
`projects/` recursively throughout v20 and the date-window skips (#1665,
#1794) do not apply to unbounded reports, so they are not classes.
Probing each ref's source for the reader code was rejected: the same
strings appear in tests and comments.

Reported by Cubic on #2061.
ryoppippi added a commit that referenced this pull request Nov 10, 2026
#2061)

* build(perf): shape the large fixture like current Claude Code and Codex logs

The CI performance comparison missed the double parse that #1719 added
to every Claude line containing `null`, because the generated fixture
never contained `null`: each line was a minimal assistant usage record
padded with a single text block of `x` characters. Real Claude Code
lines now carry `stop_sequence`, `container`, `stop_details` and other
null-valued fields, nested `usage.iterations` and cache-creation TTL
splits, and most lines are not usage records at all. On the old fixture
v20.0.20 -> v20.0.21 moved CPU time by +8%; on real-shaped lines it is
+55%.

The generator now writes sessions whose line types, per-type byte
share, and field shapes follow aggregate statistics taken from real
logs (structure only; every value is synthesized from a seeded PRNG,
so the output is deterministic):

- Claude: user/tool-result, attachment, api-request(-blob) and metadata
  lines around assistant responses written one line per content block
  with repeated usage, null-valued fields, `usage.iterations` including
  occasional advisor iterations, 5m/1h cache-creation split, escaped
  and non-ASCII text, 48 projects, sidechain `subagents/agent-*.jsonl`
  files that replay parent message IDs, and legacy files with
  `progress` lines wrapping AgentProgress assistant messages.
- Codex: dated `sessions/YYYY/MM/DD/rollout-*.jsonl` files with
  session_meta, turn_context, reasoning, tool call/output and
  item_completed lines, plus token_count (with null-heavy rate limits)
  and token_usage_record per response. As in real Codex homes, rollouts
  older than two weeks are zstd-compressed (`.jsonl.zst`) and some sit
  in `archived_sessions/`; on real data about 90% of Codex bytes are
  now read through zstd, a path the old fixture never touched.

The file-size distribution (of uncompressed bytes) and the CLI are
unchanged.

* build(perf): build base and PR from source and judge them by CPU time

The PR performance comment could not show a regression or a real
improvement:

- Unequal artifacts. Base ran the published `ccusage` npm package (Node
  launcher spawning the native binary) while the Rust leg ran the bare
  native binary, so every PR showed a ~2.6x "win" on the committed
  fixtures that was only launcher startup.
- One sample. The large fixture ran with 0 warmups and 1 run, and the
  base and PR runs were not interleaved, so runner noise dominated.
- The committed fixtures are a few KiB and measure process startup.
- No verdict. Nothing said whether a number was a regression.

The job now builds both binaries with `nix build .#ccusage-static`, the
derivation the Linux x64 native package ships, on the same runner: the
PR head from the checkout and the base from
`github.event.pull_request.base.sha`, the same base commit as before.
The walk back over pricing-only commits only existed to find a commit
with a pkg.pr.new package, so it goes. The job reuses the
build-native-packages sticky disk for dependency artifacts and no
longer waits for pkg.pr.new, so the JS launcher leg, the
committed-fixture section, and the package diagnostics are gone too.

The harness runs `claude daily`, `claude session`, `codex daily` and
the unified `daily` over the realistic generated fixture in interleaved
rounds (each round one hyperfine invocation with both binaries, order
rotated), recording wall time, CPU time, peak RSS and, where the
platform exposes it, instructions. It reports medians with min-max
spread and per-round PR/base ratios. A command is flagged when the
median CPU-time or peak-RSS ratio moves past 10% and at least four in
five rounds agree; the verdict line and a GitHub warning annotation say
so. Wall time is shown but not judged: it moves with runner load, while
CPU time paired within a round does not.

The check warns rather than fails: a failing check needs a calibrated
false-positive rate across many runs on these runners and an escape
hatch for intentional trade-offs, and neither exists yet.

* build(perf): add `just bench-history` to compare refs

Finding which release introduced a slowdown meant building each tag by
hand and timing it ad hoc. `just bench-history <ref|binary>...` builds
each ref in release mode into `rust/target/bench-history` (one shared
cargo target dir, binaries cached by commit), generates the realistic
fixture once per generator revision and size, and runs every report
command over all refs in the same interleaved rounds as the PR
comparison. Each ref is judged against the previous one with the same
CPU-time/peak-RSS rule, so a regression shows up at the step that
introduced it. `--real` adds a section over the logs the current
environment points at.

It reuses the PR harness modules instead of adding a parallel tool, and
the profile skill now points at it.

* fix(perf): print the regression annotation on stdout

GitHub Actions reads workflow commands only from stdout, but the
`::warning` line was printed while `*out*` was bound to `*err*`, so a
flagged regression never produced an annotation. Print it on stdout and
pass the GitHub Actions check in, so a test can assert the stream.

Reported by Pullfrog on #2061.

* fix(perf): reject bench-history runs below the minimum

`parse-history-cli` validated a copy with `:runs` raised to 3 but
returned the original options, so `--runs 2` passed validation and then
measured two rounds, too few for the four-in-five agreement rule.
Validate the options that are returned, so fewer than three rounds is
rejected as in the PR comparison, and say so in both `--runs` helps.

Reported by Pullfrog on #2061.

* fix(perf): run fixture benchmarks in an empty environment

The fixture runs pointed Claude and Codex at the fixture and moved HOME
and XDG_* to an empty directory, but every other agent has its own
data-dir variable (AMP_DATA_DIR, OPENCODE_DATA_DIR, PI_AGENT_DIR,
GEMINI_DATA_DIR and a dozen more), and a value set in the caller's shell
still pointed the unified `daily` report at real logs. Listing every
variable would rot as agents are added, so the fixture commands now run
under `env -i` with only the variables the run needs; with XDG_* unset
the agents fall back to the empty HOME. The local-logs mode keeps the
caller's environment on purpose.

Reported by Cubic on #2061.

* fix(perf): make bench-history safe to point at binaries and run twice

Four ways `just bench-history` could measure the wrong thing or fail:

- Binary-path variants were labelled by basename, so two builds at
  `a/ccusage` and `b/ccusage` collided in hyperfine's results. Label
  them by the argument as given, and reject a ref or path passed twice.
- `--fixture-dir` alone silently reused the Claude logs as the Codex
  fixture. Require both directories or neither.
- The cached binary was copied without attributes; copy them so the
  executable bit is kept on every platform.
- Every run checked refs out into the shared `<cache>/src` worktree and
  removed it on exit, so a second run sharing the cache deleted the
  first run's checkout mid-build. Each run now uses its own temporary
  worktree; the shared cargo target is safe because cargo locks it.

The first three were reported by Cubic on #2061; the fourth showed up
while bisecting with two runs at once.

* fix(perf): report memory-only improvements and history instruction ratios

The overall verdict counted only CPU improvements, so a PR that cut
peak RSS read "no significant change" above a table saying "less
memory". Count CPU and RSS improvements alike and name what improved
per command. The history tables also dropped the instruction-count
ratio the PR comparison shows; add it as a column whenever hardware
counters are available.

Reported by Cubic on #2061.

* test(perf): restore duplicate-name and time-unit hyperfine cases

The hyperfine reader rewrite kept the missing-name and malformed-output
cases but lost the duplicate-name and non-second-unit ones, so a change
that accepted ambiguous or mis-scaled samples would pass. Restore both
against the new per-run reader.

Reported by Cubic on #2061.

* fix(perf): print the generated fixture's size on disk

With zstd-compressed Codex rollouts the generator's `Size` line showed
the uncompressed bytes, about four times what it wrote. Print the size
on disk from `Bun.write` and the uncompressed size separately.

Reported by Cubic on #2061.

* fix(ci): measure the PR merged onto its base commit

The job built the PR side from `pull_request.head.sha` and the base
from `pull_request.base.sha`. When a branch is behind its base, the two
binaries differ by the base's newer commits as well as the PR's own
change, and those commits show up as PR regressions or improvements.

Build the checked-out merge commit (`github.sha`) instead. It is the PR
applied on top of the base commit, so the diff measured is exactly the
PR. A step fails the job if the base is not an ancestor of the built
commit, so the report's "merged onto Base:" line always names what was
actually built. BASE_SHA/HEAD_SHA fall back to `merge_group.base_sha`
and `github.sha`, and the PR-comment step is limited to pull_request
events, so the job also works if a merge_group trigger is added.

Reported by CodeRabbit on #2061.

* fix(perf): cache built bench-history binaries atomically

`build-commit!` copied the release binary straight to the cache path
that later runs trust when it exists, so an interrupted copy left a
truncated binary that every later run reused. Copy to a `.partial`
sibling and rename it into place with an atomic move.

Reported by CodeRabbit on #2061.

* fix(perf): start fixture generation from an empty directory

A cached fixture without its `.complete` marker was regenerated into
the same directory, so files from an interrupted run could remain next
to the new output. Delete the directory first, so the marker always
describes exactly what the generator wrote.

Reported by CodeRabbit on #2061.

* fix(perf): order Codex usage records like real rollouts and add compactions

The generator wrote each response's `token_count` before its
`token_usage_record`. Real rollouts write the record first, and the
parser settles a pending record only when a later `token_count`
advances the cumulative total, so every synthetic record stayed in the
compaction pending map until the end of its file. That inflated peak
RSS in a way real logs never do: on the corrected fixture `codex daily`
peaks at 56 MiB on main instead of 69 MiB.

Write the record before the `token_count`, as Codex does, and add the
remote compactions real rollouts contain: about one turn in 30 ends
with a usage record whose usage is not in the cumulative total,
followed by a `compacted` marker naming it and carrying a large
replacement history. In real logs every marker names an earlier record.

Reported by Cubic on #2061.

* fix(ci): gate the perf job on preflight and keep its token read-only

The old perf job depended on the pkg.pr.new publish job and so, through
it, on `preflight` (secret scan and workflow lint); building from
source dropped that dependency, letting a PR that fails the scan run
the benchmark anyway. Depend on `preflight` directly.

The job also ran PR-built binaries, the fixture generator and the
harness with `pull-requests: write` and `issues: write`. Split it:
`ccusage-perf` builds and measures with a read-only token and uploads
the report, and `ccusage-perf-comment` downloads it and only runs the
comment upsert with the write token.

Reported by Cubic on #2061.

* fix(perf): lock bench-history builds and fixture generation

Runs sharing the cache could interleave writes: one build could replace
`target/release/ccusage` in the shared cargo target before another run
copied it into its commit's cache slot, and two runs could delete and
regenerate the same fixture under each other. Build-and-copy now runs
under a cache-wide file lock, and each fixture key under its own, both
re-checking the cache once they hold the lock. A per-commit target dir
would avoid the lock but rebuild every dependency for every ref.

The lock is a FileChannel lock released by closing the channel, and a
test runs three processes against it and checks their critical
sections never overlap.

Reported by Cubic on #2061.

* fix(ci): drop the perf job's unreachable merge_group fallbacks

The perf job only runs on `pull_request`, and #2062, which adds the
`merge_group` trigger, keeps the perf comment PR-only, so the
`merge_group.base_sha` and `github.sha` fallbacks and the comment
promising merge_group support could never apply. Remove them.

Reported by Cubic on #2061.

* fix(perf): give bench-history a Codex fixture every compared ref reads

The generated Codex fixture is mostly zstd-compressed rollouts, some in
`archived_sessions/`. Releases before #1890 skip `.jsonl.zst` files
(and before #1230 skip archived sessions), so a history run across
that change compared an old ref reading a fraction of the input with a
new ref reading all of it, and reported the difference as a CPU and
RSS regression.

The generator gains `--codex-layout legacy`, which writes the same
rollouts uncompressed under `sessions/`. bench-history keeps the
current layout when every ref is a commit containing #1890, and
otherwise generates the legacy layout and says so at the top of the
report; binary paths count as unknown. With a custom Codex fixture
that holds `.zst` files and a ref that cannot read them, and for
`--real` runs across the change, the report leads with a warning
instead. The PR comparison always builds refs that read the current
layout, so CI keeps compressed coverage.

Reported by CodeRabbit on #2061.

* fix(perf): check every Codex input class a compared ref may skip

The compatibility check only looked for `.jsonl.zst` files, but
releases before #1230 also skip `archived_sessions/`, so a custom
fixture with archived plain rollouts got no warning and a history run
across #1230 compared different input.

Replace the single zstd commit with a table of Codex input classes taken
from the Codex loader's history (`paths.rs`, `reader.rs`): archived
sessions (a9b12c4, #1230, v20.0.7) and zstd-compressed rollouts
(44a3aa8, #1890, v20.0.33). Each ref is checked per class by git
ancestry; the generated fixture switches to the legacy layout when any
ref skips any class, and a custom fixture or `--real` run warns per
class with the refs that skip it. Claude discovery has scanned
`projects/` recursively throughout v20 and the date-window skips (#1665,
#1794) do not apply to unbounded reports, so they are not classes.
Probing each ref's source for the reader code was rejected: the same
strings appear in tests and comments.

Reported by Cubic on #2061.

* fix(ci): run the perf comment upsert from the base commit

The comment job holds `pull-requests: write` and `issues: write`, but it
checked out the PR merge commit and ran that tree's setup action and
`upsert-pr-comment.nu`, so a PR that edited either ran them with the
write token. Check out `pull_request.base.sha` instead, still without
persisted credentials. The PR's report reaches the job only as an
artifact the script reads as text.

This PR does not change `upsert-pr-comment.nu`, and the base version
reads the same inputs (COMMENT_FILE as raw markdown, COMMENT_MARKER,
PR_NUMBER, GITHUB_REPOSITORY, GITHUB_TOKEN), so the first runs after
merge need no fallback.

The workflow file itself still comes from the PR on `pull_request`
events, so this removes the PR's scripts from the write job rather than
making the job a hard trust boundary.

Reported by CodeRabbit on #2061.

* fix(perf): match Codex input classes to the rollouts the loader reads

The input-class matchers looked at absolute paths: any `.zst` file
counted as a compressed rollout, and any path containing
`/archived_sessions/`, including a parent of the fixture, counted as
an archived session. Match paths relative to the Codex home against
what the loader reads: `*.jsonl` and `*.jsonl.zst` (reader.rs) under
`sessions/` and `archived_sessions/` (paths.rs). Tests cover
non-rollout `.zst` files, rollouts outside those directories,
non-rollout files in `archived_sessions/`, and a fixture under a
directory named `archived_sessions`.

Reported by Cubic on #2061.
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