Skip to content

fix(opencode): honor --since/--until in the SQLite loader and JSON scan - #1492

Merged
ryoppippi merged 9 commits into
mainfrom
fix/opencode-since-until-adapter
Jul 27, 2026
Merged

fix(opencode): honor --since/--until in the SQLite loader and JSON scan#1492
ryoppippi merged 9 commits into
mainfrom
fix/opencode-since-until-adapter

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jul 26, 2026

Copy link
Copy Markdown
Member

Summary

ccusage opencode (and the aggregate ccusage) read every row of the OpenCode SQLite message table and re-parsed every storage/message/**/*.json file on each invocation, no matter what --since / --until asked for. SharedArgs.since / SharedArgs.until were already plumbed into the adapter; the loader simply never looked at them, so the date window was only applied after everything had been loaded. On long-lived installs, especially those carrying the pre-SQLite JSON dump, the loader can run for minutes on a query that covers a few days.

This PR narrows the work in the loader, without changing what a given window means.

Behavior

Bound semantics are unchanged: both ends stay inclusive, and the authoritative decision is still the compact YYYYMMDD string comparison every report already performed. The loader now skips work that comparison would have discarded anyway, so output is byte-identical to main (see Verification).

Implementation

rust/crates/ccusage/src/date_utils.rs

  • date_range_bounds_ms resolves --since / --until to half-open millisecond bounds [since 00:00, day-after-until 00:00) in the reporting timezone, so a window means the same instants the report means. DST days resolve through jiff, so a spring-forward day is 23 hours long and a midnight that does not exist moves to the first instant that does.
  • parse_compact_date validates a bound byte-wise. Anything that is not eight ASCII digits (partial bounds like 202601, multi-byte input, 20260231) yields no instant, which leaves that side of the window open and defers entirely to the string filter.
  • date_within_range is now the single definition of the window rule, shared by the loader pre-filter, filter_loaded_entries_by_date, and filter_and_sort_summaries. It previously existed as three separate copies.

rust/crates/ccusage/src/adapter/opencode/loader.rs

  • The prepared statement applies those instants through a subquery: WHERE id IN (SELECT id FROM message WHERE time_created >= ?1 AND time_created < ?2), or an open-ended variant. Selecting id alone lets the existing (session_id, time_created, id) index answer the bound as a covering scan, so out-of-range rows never have their data blob read. A bare WHERE time_created >= ?1 cannot seek that index — its leading column is session_id — and SQLite falls back to a table scan that pulls every payload through the page cache, which is what made the first revision of this branch far slower than it needed to be.
  • Rows and JSON files that survive the push-down get a cheap time.created scan of the raw text before the full serde_json parse; out-of-range ones are dropped without being deserialized.
  • Both checks are integer comparisons against the precomputed bounds. When no window is requested they are skipped entirely.
  • The bounds handed to SQL are widened by a day on each side. time_created mirrors the payload's time.created on every build checked here, but the column is only a proxy for the payload the report filters on, so a loose SQL window means a drifting column costs extra rows scanned rather than excluding a row the report wanted. The exact window still runs per row.

Correctness safeguards

Every pre-filter fails open, so none of them can drop an entry the report would have kept:

Situation Behavior
Bound is not a full date (partial, multi-byte, invalid) No instant, that side is not narrowed, string filter decides
Legacy schema with no time_created column Filtered query fails to prepare, falls back to an unfiltered scan
time_created is not millisecond-scale (e.g. seconds, zeroes) Push-down disabled, falls back to an unfiltered scan
Payload has no parseable time.created Pre-parse check declines, full parse decides
Non-UTF-8 message file Pre-parse check skipped, full parse decides
Window excludes every entry Source detection asks the filesystem, so the aggregate report still lists OpenCode
Payload carries another object's time before its own The scan stays inside the first time object, finds no created, and the full parse decides
time_created drifts from its payload by up to a day Absorbed by the SQL margin; the payload check still decides

On the schema note: message.time_created repeats the payload's time.created on the builds checked here (max delta 0 across 600 local rows), which is what makes the push-down sound. The scale check is there because a column holding seconds compared against millisecond bounds would silently exclude every row — an empty report is a much worse failure than a slow one.

Performance

Synthetic fixture (OPENCODE_DATA_DIR), release builds, hyperfine:

  • opencode.db: 200,000 rows, 0.83 GB, current schema and index
  • storage/message/: 20,000 JSON files (legacy dump)
  • Data spans 400 days; the query below covers 7 of them

Measured against an origin/main-equivalent build, with the day of SQL margin in place:

Query before after speedup
7-day window, database only 1.133 s ± 0.140 34.0 ms ± 4.3 33.3×
7-day window, database + legacy JSON dump 1.558 s ± 0.174 363.7 ms ± 37.8 4.3×
no window (regression check) 1.715 s ± 0.123 1.540 s ± 0.214 1.11×

The fixture fits in the page cache, so a multi-gigabyte install should widen the database-only gap rather than narrow it; that has not been measured here. The remaining ~360 ms in the second row is the legacy dump's file reads, which still happen — only the parse is skipped.

Token totals are identical between the two builds for the measured window.

Verification

  • cargo test --workspace: 495 tests pass (after merging current main). cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --all -- --check are clean.
  • Output parity against an origin/main-equivalent build on a local install (600 DB rows, 437 JSON files): opencode daily and session JSON compared across 12 window shapes — including a partial bound and a multi-byte one — plus every present date individually in Pacific/Kiritimati (UTC+14), Etc/GMT+12, UTC, Asia/Tokyo and America/New_York: 67 comparisons, all byte-identical. The Claude-side daily, session and blocks JSON were compared as well, to cover the shared date_within_range refactor: 6 comparisons, identical (session output is 1.6 MB).
  • extract_message_timestamp was checked against 1037 real records (600 DB rows + 437 files): it agrees with a full JSON parse on every one, and never mis-extracts.
  • ccusage opencode --since "abあcde" used to abort with byte index 4 is not a char boundary (and agent loader panicked through the aggregate command). It now behaves like main and reports no matching data.
  • ccusage --since 20200101 --until 20200102 (a window with no data) lists the same Detected: agents as before the change, OpenCode included.

Notes for reviewers

  • An earlier revision of this branch skipped JSON files by mtime. That was dropped: mtime is not the payload date (a restored or copied dump has the wrong one), so the file's own time.created is used instead. Files are still read, but out-of-range ones are no longer parsed.
  • An earlier revision also widened the SQL bounds by a day of slack to absorb timezone skew. Resolving the bounds in the reporting timezone removes the need for slack entirely, so the SQL window and the report's window are now the same window.
  • The scale sanity check costs one query answered from the covering index with LIMIT 8; it does not touch the data column. It proves the column shares a unit with the payload, not a value — sub-day drift is handled by the SQL margin instead. A column with genuinely mixed scales, or drift beyond a day, would need a full-column scan to detect, which is the cost this push-down exists to avoid; the local install shows a maximum delta of 0 across 600 rows.
  • Source detection judges directory entries with file_type(), the same as collect_files_with_extension, so the two passes agree about symlinks: detection never claims a source whose files the collection pass would refuse to read.

Relationship to #1188

This supersedes #1188 and keeps its first commit with the original authorship (@justi). That PR identified the gap and the push-down approach; the commits on top of it address review findings:

  • a panic on multi-byte --since / --until values, which also took down the aggregate ccusage command
  • the day of slack and the hand-rolled YYYYMMDD arithmetic it needed, replaced by timezone-resolved instants
  • three separate copies of the window rule, collapsed into one
  • the SQL shape, which was scanning the table rather than using the index
  • the aggregate report dropping OpenCode from its Detected: list whenever the window excluded every entry, since that list is derived from pre-filter entries

No CLI surface, docs, or configuration schema changes.

History

The same user-visible gap was reported in #801, requested in #867, and previously fixed against the now-removed TypeScript @ccusage/opencode package in #960 (closed with: "If a gap remains, it needs a fresh narrow PR against main").

Summary by CodeRabbit

  • New Features

    • Added more accurate date-range filtering for usage data, including timezone-aware boundaries and daylight-saving transitions.
    • Improved OpenCode data loading with earlier filtering for faster results.
    • OpenCode sources are now recognized even when no records fall within the selected date range.
  • Bug Fixes

    • Fixed date filtering across SQLite and JSON-based OpenCode data.
    • Improved handling of legacy databases, timestamp formats, invalid files, and boundary dates.

justi and others added 5 commits June 22, 2026 12:16
Push date bounds into the OpenCode loader instead of reading every DB
row and JSON file on each invocation, which hangs on large installs.

- SQLite: indexed `WHERE time_created` push-down with -1d/+2d slack,
  falling back to an unfiltered scan when the schema lacks the column
  (the in-loop date check still applies, so no row is silently dropped).
- JSON files: extract `time.created` from the raw payload and apply the
  same `format_date_tz` check as the authoritative
  `filter_loaded_entries_by_date`, failing open to a full parse so no
  in-range row is ever dropped.

Combines the indexed SQL push-down from #1188 with the content-based
JSON extraction from #1220, per maintainer guidance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first pass hand-parsed the YYYYMMDD bounds with byte slicing and then
widened them by a day of slack. Two problems came out of that:

- Byte slicing panicked on any multi-byte bound. `ccusage opencode
  --since "abあcde"` aborted with "byte index 4 is not a char boundary",
  and through the aggregate command it became `agent loader panicked`,
  killing the whole report. `--since`/`--until` are unvalidated free-form
  strings, so this was reachable from the CLI and from config files.
- The slack needed a paragraph of comment to argue that the pre-filter
  stayed wider than the report's own filter.

Resolve both bounds to half-open millisecond instants in the reporting
timezone instead, via a shared `date_range_bounds_ms` helper:

- `parse_compact_date` validates bounds byte-wise, so a bound that is not
  eight ASCII digits leaves that side of the window open and the report's
  string filter decides, exactly as it did before this branch.
- The SQL push-down and the pre-parse row check use those instants
  directly, so there is no slack to justify, and the per-row check is an
  integer comparison rather than a formatted string plus an allocation.
- `date_within_range` now backs the loader pre-filter, the loaded-entry
  filter and the summary filter, so the window rule has one definition
  instead of three copies.

Disable the push-down when `message.time_created` is not millisecond
scale. The column repeats the payload's `time.created` on every OpenCode
build checked (max delta 0 over 600 local rows), but a second-scale
column compared against millisecond bounds would silently exclude every
row instead of merely scanning more than needed.

Drop the "indexed" claim from the comments: the only index is
(session_id, time_created, id), so a bare time_created range cannot seek
it and SQLite reports SCAN message. The push-down still pays off by not
materializing and parsing `data` for out-of-range rows.

Tests: the SQLite helper now mirrors the real schema, with `time_created`
repeating the payload timestamp, so the push-down is what most database
tests exercise; a separate legacy-schema helper covers the unfiltered
fallback. New coverage pins the timezone-local bounds at UTC+14 and
UTC-12, a spring-forward day being 23 hours long, the second-scale
fallback, multi-byte bounds, and `extract_message_timestamp` directly.
`WHERE time_created >= ?` cannot seek OpenCode's only index,
(session_id, time_created, id), because its leading column is session_id.
SQLite therefore scanned the table and pulled every `data` blob through
the page cache just to evaluate the bound. Moving the bound into a
subquery that selects `id` alone lets the index answer it as a covering
scan, leaving only in-range rows to be fetched by primary key.

Measured on a synthetic database with the real schema and index, 200k
rows and 0.83 GB spanning 400 days, querying a 7-day window:

- database only: 949.6 ms -> 32.1 ms (29.6x)
- with a 20k-file legacy JSON dump alongside: 1.584 s -> 300 ms (5.3x),
  the remainder being file reads the loader still performs
- no window at all: 1.422 s -> 1.236 s, so the unfiltered path is not
  penalized

Token totals are identical between both builds for every variant, and the
67-comparison real-data parity sweep still reports no differences.
The aggregate report builds its "Detected:" list from whether each
adapter returned any entries before date filtering. Now that the OpenCode
loader narrows to the window as it reads, a query matching nothing made
an installed OpenCode vanish from that list, unlike every other adapter.

Ask the source directly instead, the way the qwen adapter already does:
`has_source` looks for the SQLite database, then for the first message
file, and stops there — so detection stays cheap next to a large legacy
dump.

Reported by a codex review of this branch.
Copilot AI review requested due to automatic review settings July 26, 2026 22:14
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 26, 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 29d792b Commit Preview URL

Branch Preview URL
Jul 27 2026, 11:36 AM

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Shared date-window utilities now drive summary and loaded-entry filtering. OpenCode applies timezone-aware filtering during SQLite and JSON loading, adds timestamp extraction and SQL pushdown, and preserves source detection when all data falls outside the requested window.

Changes

OpenCode date-window handling

Layer / File(s) Summary
Date-window contract and shared filtering
rust/crates/ccusage-core/src/date_utils.rs, rust/crates/ccusage-core/src/summary.rs, rust/adapters/common/src/lib.rs
Compact-date parsing, inclusive date checks, timezone-aware millisecond bounds, and shared filtering are centralized in ccusage_core.
Windowed OpenCode loading
rust/adapters/opencode/src/loader.rs
SQLite and JSON loading receive a computed date window, use timestamp extraction and SQL pushdown where available, fall back for legacy schemas, and validate the behavior with expanded tests.
Source detection wiring
rust/adapters/opencode/src/lib.rs, rust/adapters/opencode/src/loader.rs, rust/crates/ccusage-adapter-all/src/loader.rs
OpenCode source detection checks databases and message files independently of date bounds, and aggregate detection preserves the source status.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant load_entries_from_directory
  participant DateWindow
  participant SQLite_loader
  participant JSON_message_loader
  load_entries_from_directory->>DateWindow: derive bounds from SharedArgs and timezone
  DateWindow->>SQLite_loader: pass date window
  DateWindow->>JSON_message_loader: pass date window
  SQLite_loader->>SQLite_loader: push down query and check payload timestamps
  JSON_message_loader->>JSON_message_loader: extract timestamps before JSON parsing
Loading

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: OpenCode now honors --since/--until during SQLite loading and JSON scanning.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ 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 fix/opencode-since-until-adapter

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.

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ 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.

@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 — pushes --since/--until into the OpenCode SQLite and JSON loader so the date window is applied during load rather than after, with a shared window rule, timezone-resolved bounds, index-aware SQL, and fail-open fallbacks.

  • Push date window into SQLite via covering-index subqueryWHERE id IN (SELECT id FROM message WHERE time_created >= ?1 AND time_created < ?2) answered from (session_id, time_created, id) without reading data blobs; falls back to unfiltered scan when schema lacks time_created or the column isn't millisecond-scale.
  • Skip out-of-range JSON files before full parseextract_message_timestamp scans raw text for time.created millis; fails open on non-UTF-8, missing key, or quoted/negative numbers so the full parse decides.
  • Unify the date window ruledate_within_range replaces three separate copies in summary.rs, claude/mod.rs, and the old opencode inline logic.
  • Keep OpenCode in the aggregate report's detected agentsopencode::has_data() checks source presence independently of the date window so out-of-range queries don't drop OpenCode from Detected:.
  • Prevent panic on multi-byte boundsparse_compact_date works byte-wise on value.as_bytes() so --since "abあcde" no longer panics with an index-boundary error.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@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 `@rust/crates/ccusage/src/adapter/opencode/loader.rs`:
- Around line 136-149: Update has_json_file to inspect each directory entry with
entry.file_type() rather than path.is_dir(), treating only actual directories as
recursive candidates and skipping symlinks. Preserve the existing JSON-extension
check for non-directories and the current read-directory failure behavior.
- Around line 310-323: Update extract_message_timestamp to match only the
canonical `"time":{"created":...}` key shape at the top-level JSON structure,
rather than any occurrence inside payload text. Limit the created-value scan to
the enclosing time object and require its valid closing boundary, returning None
for malformed or unterminated objects so incorrect timestamps never cause
entries to be skipped.
🪄 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 Plus

Run ID: be2cf0bc-73fa-402b-b227-7a1e5906bf04

📥 Commits

Reviewing files that changed from the base of the PR and between df89eb7 and cfed08d.

📒 Files selected for processing (6)
  • rust/crates/ccusage/src/adapter/all/loader.rs
  • rust/crates/ccusage/src/adapter/claude/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/loader.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/date_utils.rs
  • rust/crates/ccusage/src/summary.rs

Comment thread rust/adapters/opencode/src/loader.rs
Comment thread rust/adapters/opencode/src/loader.rs

@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 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="rust/crates/ccusage/src/adapter/opencode/loader.rs">

<violation number="1" location="rust/crates/ccusage/src/adapter/opencode/loader.rs:410">
P1: Mixed-scale historical rows can still be dropped because this arbitrary eight-row sample treats the whole column as milliseconds. Make the pushdown fail open for any unverified scale values, or remove the exclusionary predicate when scale consistency cannot be established.</violation>
</file>

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

Re-trigger cubic

/// the push-down and leaves the payload check to filter.
fn time_created_is_millis(connection: &sqlite::Connection) -> bool {
let Ok(mut statement) = connection
.prepare("SELECT max(time_created) FROM (SELECT time_created FROM message LIMIT 8)")

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.

P1: Mixed-scale historical rows can still be dropped because this arbitrary eight-row sample treats the whole column as milliseconds. Make the pushdown fail open for any unverified scale values, or remove the exclusionary predicate when scale consistency cannot be established.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/opencode/loader.rs, line 410:

<comment>Mixed-scale historical rows can still be dropped because this arbitrary eight-row sample treats the whole column as milliseconds. Make the pushdown fail open for any unverified scale values, or remove the exclusionary predicate when scale consistency cannot be established.</comment>

<file context>
@@ -221,6 +307,118 @@ fn entry_id(entry: &LoadedEntry) -> Option<&str> {
+/// the push-down and leaves the payload check to filter.
+fn time_created_is_millis(connection: &sqlite::Connection) -> bool {
+    let Ok(mut statement) = connection
+        .prepare("SELECT max(time_created) FROM (SELECT time_created FROM message LIMIT 8)")
+    else {
+        return false;
</file context>

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.

Fully detecting mixed-scale columns would need a full-column min/scan, which is exactly the cost the LIMIT-8 sample avoids on tens-of-GB databases; the code already fails open for uniform non-millisecond columns and the authoritative payload recheck bounds any residual risk, so this is a conscious sampling trade-off rather than a minimal-fix bug.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Accurate, and left in place deliberately — recording the reasoning here so it is next to the code rather than only in the PR thread.

Detecting mixed scales reliably needs a full-column scan, which is the work this push-down exists to avoid: on the 33 GB install from the original report, that scan is the bug. The LIMIT 8 sample is answered from the covering index and never touches data, which is what makes it affordable.

What bounds the risk:

  • a uniformly non-millisecond column disables the push-down entirely and falls back to an unfiltered scan
  • 7ee95ee widens the pushed-down bounds by a day, so sub-day column/payload drift can no longer cause a SQL-level exclusion
  • the payload's time.created remains authoritative for every row that is returned

What stays exposed: genuinely mixed scales within one column, or drift beyond a day. Neither appears on any OpenCode build I can inspect — the column and payload are written in the same statement, and a local install shows a maximum delta of 0 across 600 rows.

2e1af2d renamed the helper to time_created_looks_like_millis and rewrote its doc to say the sample proves nothing about rows it did not read, so the limitation you flagged is now stated where a reader will find it.

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.

The parent comment was too broad for this PR. The sampling heuristic is intentional here: it avoids the full-column scan that would be the real bug on large installs, and the code now states its limits next to the helper. The payload check still remains authoritative, so the windowing change only affects how much data gets scanned, not which entries survive.

Comment thread rust/adapters/opencode/src/loader.rs
Comment thread rust/crates/ccusage/src/adapter/opencode/loader.rs Outdated
Comment thread rust/crates/ccusage/src/adapter/opencode/loader.rs Outdated
@pkg-pr-new

pkg-pr-new Bot commented Jul 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

ccusage

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

@ccusage/ccusage-darwin-arm64

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

@ccusage/ccusage-darwin-x64

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

@ccusage/ccusage-linux-arm64

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

@ccusage/ccusage-linux-x64

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

@ccusage/ccusage-win32-x64

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

commit: 29d792b

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: cfed08d45ac8
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 361.9ms 2.78 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 317.4ms 3.17 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 116.0ms 8.68 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 92.0ms 10.94 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 native ccusage binary 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 31.0ms 5.0ms 6.19x 55.00 MiB 12.20 MiB 0.22x 0.05 MiB/s 0.31 MiB/s
claude session --offline --json 0.00 MiB 27.1ms 3.3ms 8.22x 55.00 MiB 12.21 MiB 0.22x 0.06 MiB/s 0.47 MiB/s
codex daily --offline --json 0.00 MiB 27.5ms 2.3ms 11.93x 55.00 MiB 10.45 MiB 0.19x 0.03 MiB/s 0.37 MiB/s
codex session --offline --json 0.00 MiB 24.9ms 2.4ms 10.58x 55.00 MiB 10.45 MiB 0.19x 0.03 MiB/s 0.36 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 369.3ms 302.9ms 1.22x 952.33 MiB 972.59 MiB 1.02x 2.73 GiB/s 3.32 GiB/s
codex --offline --json 1.01 GiB 116.8ms 97.2ms 1.20x 408.89 MiB 406.89 MiB 1.00x 8.62 GiB/s 10.35 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.70 KiB -0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.63 KiB +11.69 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

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: cfed08d45ac8
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 386.5ms 2.61 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 313.3ms 3.21 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 116.8ms 8.62 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 92.9ms 10.84 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 26.4ms 26.7ms 0.99x 55.00 MiB 55.00 MiB 1.00x 0.06 MiB/s 0.06 MiB/s
claude session --offline --json 0.00 MiB 26.4ms 25.8ms 1.03x 55.00 MiB 55.00 MiB 1.00x 0.06 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 27.9ms 23.5ms 1.19x 55.00 MiB 55.00 MiB 1.00x 0.03 MiB/s 0.04 MiB/s
codex session --offline --json 0.00 MiB 23.2ms 23.0ms 1.01x 55.25 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.04 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 376.3ms 361.0ms 1.04x 954.34 MiB 954.33 MiB 1.00x 2.68 GiB/s 2.79 GiB/s
codex --offline --json 1.01 GiB 118.2ms 136.6ms 0.87x 416.91 MiB 410.90 MiB 0.99x 8.52 GiB/s 7.37 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.70 KiB -0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.63 KiB +11.69 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.

…object

Two findings from a CodeRabbit review of this branch.

`extract_message_timestamp` searched for the first `"time"` anywhere in the
payload and then for `"created":` without a bound, so a `time` object
belonging to something else could contribute the number. A failed scan is
harmless — the caller falls back to a full parse — but a wrong one silently
drops an in-range entry. Require the canonical `"time":` key, require the
object to open right after it, and keep the `created` search inside that
object. Checked against 1037 real records (600 database rows, 437 message
files): still agrees with a full parse on every one, with no fallbacks.

`has_json_file` judged entries with `path.is_dir()`, which follows
symlinks, while the collection pass it feeds uses `file_type()` and skips
them. Detection could therefore claim files that would never be read, and
a symlinked cycle would recurse until the stack gave out. Match
`collect_files_with_extension` instead.
Copilot AI review requested due to automatic review settings July 26, 2026 22:30

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai thanks — both findings were valid and are fixed in d72e237.

Raw-text scan latching onto an unrelated "time" (Major)

Agreed, and the asymmetry you point out is the important part: a scan that gives up is free (the caller falls back to a full parse), but a scan that guesses wrong silently drops an in-range entry. Applied your tightening: require the "time": key, require the object to open right after it, and keep the created search inside that object.

I re-validated against real data rather than just the unit tests — 1037 records from a local install (600 message rows plus 437 legacy storage/message/**/*.json files). The tightened scan still agrees with a full serde_json parse on every record, with zero fallbacks, so the fast path is not lost on real payloads.

Also added the two cases that matter for the false-positive class:

  • {"parts":[{"time":{"start":1}}],"time":{"created":...}}None, since the first time object has no created; the full parse then decides
  • {"unit":"time","created":...}None, since a "time" used as a value is not followed by a colon

And you were right about the test expectation: the unterminated r#""time":{"created": 42"# case now reads {"time":{"created": 42}}. The unwrap_or(after_key.len()) branch is still needed and still covered, because the slice is cut at } before the digits are scanned.

has_json_file following symlinks (Minor)

Fixed. Worth noting it was not only a robustness issue: the collection pass this detection feeds (collect_files_with_extension) judges entries with file_type() and skips symlinks, so detection could report a source whose files would never actually be read — "detected, but zero entries". Both now use file_type(), so they agree.

Verification for this push

  • cargo test --workspace 494 pass; cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --all -- --check clean
  • Output parity against an origin/main-equivalent build on real data: 67 comparisons (every present date in Pacific/Kiritimati, Etc/GMT+12, UTC, Asia/Tokyo, America/New_York, plus 12 window shapes including a partial and a multi-byte bound) — all byte-identical
  • ccusage --since 20200101 --until 20200102 still lists OpenCode under Detected:, matching origin/main

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Tip

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

@ryoppippi Thanks for the thorough fix and validation. The conservative raw-scan behavior and the file_type() alignment between source detection and collection address both findings cleanly. The real-data parity coverage across timezones and bound shapes is especially reassuring.

🐇✅


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 — tightens has_json_file to skip symlinks via file_type() and scopes extract_message_timestamp to the message's own time object so nested time objects elsewhere in the payload cannot contribute a false match.

  • Skip symlinks in has_json_file — inspects entry.file_type() instead of path.is_dir(), matching collect_files_with_extension behavior and preventing infinite recursion on symlinked cycles.
  • Scope extract_message_timestamp to the message's own time object — parses the { ... } body immediately after "time": so a time object nested inside parts[].time cannot leak into the scan. Fails open to a full parse when the object boundaries don't line up.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: d72e23707e3b
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 344.8ms 2.92 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 309.7ms 3.25 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 113.8ms 8.85 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 91.3ms 11.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 the published native ccusage binary 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.8ms 3.7ms 8.31x 55.25 MiB 12.20 MiB 0.22x 0.05 MiB/s 0.42 MiB/s
claude session --offline --json 0.00 MiB 26.1ms 2.7ms 9.57x 55.25 MiB 12.20 MiB 0.22x 0.06 MiB/s 0.57 MiB/s
codex daily --offline --json 0.00 MiB 26.7ms 2.4ms 11.21x 55.00 MiB 10.45 MiB 0.19x 0.03 MiB/s 0.36 MiB/s
codex session --offline --json 0.00 MiB 22.9ms 2.4ms 9.48x 55.00 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.35 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 376.4ms 316.5ms 1.19x 944.34 MiB 964.58 MiB 1.02x 2.67 GiB/s 3.18 GiB/s
codex --offline --json 1.01 GiB 140.7ms 102.6ms 1.37x 408.91 MiB 410.91 MiB 1.00x 7.15 GiB/s 9.82 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.71 KiB +0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.94 KiB +12.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

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: d72e23707e3b
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 342.4ms 2.94 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 304.7ms 3.30 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 116.3ms 8.66 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 93.5ms 10.76 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.2ms 26.0ms 1.12x 55.00 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.06 MiB/s
claude session --offline --json 0.00 MiB 27.0ms 26.5ms 1.02x 55.00 MiB 55.25 MiB 1.00x 0.06 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 24.3ms 23.7ms 1.02x 54.75 MiB 54.75 MiB 1.00x 0.04 MiB/s 0.04 MiB/s
codex session --offline --json 0.00 MiB 23.3ms 23.2ms 1.00x 55.00 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.04 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 383.9ms 342.3ms 1.12x 952.34 MiB 952.33 MiB 1.00x 2.62 GiB/s 2.94 GiB/s
codex --offline --json 1.01 GiB 120.4ms 116.4ms 1.03x 424.91 MiB 414.91 MiB 0.98x 8.36 GiB/s 8.65 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.71 KiB +0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.94 KiB +12.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.

A cubic review pointed out that the push-down filters on
`message.time_created` while the report filters on the payload's
`time.created`, and the scale check only proves those share a unit, not a
value. A column drifting from its payload by a few hours would therefore
exclude a row near a window edge — silently, since SQL never hands it to
the payload check.

Widen only the pushed-down bounds by a day on each side. The exact window
still runs per row against the payload, so the margin costs a few extra
rows to scan and cannot change which entries survive. The new test fails
without it.

The remaining exposure is a column drifting by more than a day, or a
column with mixed scales that the sampled rows do not reveal. Both would
need a full-column scan to detect, which is the cost this push-down
exists to avoid; the local install shows a maximum delta of 0 across 600
rows.
Copilot AI review requested due to automatic review settings July 26, 2026 22:54

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ryoppippi

Copy link
Copy Markdown
Member Author

Addressed the review findings on the push-down in 7ee95ee. Summary of where each one landed:

Column/payload drift (cubic P1, loader.rs:393) — valid, fixed

The scale check proved the column and the payload share a unit, not a value. A column drifting a few hours from its payload would exclude a row near a window edge, and because SQL never hands that row to the payload check, the loss is silent. That is the one failure mode worth spending time on here.

Fix: the pushed-down bounds are now widened by a day on each side, while the exact window still runs per row against the payload. So the margin can only cost extra rows scanned, never change which entries survive. The new test (pushdown_margin_keeps_rows_whose_column_drifts_from_the_payload) uses a column 26 hours ahead of its payload; I confirmed it fails when the margin is removed, so it is pinning the behavior rather than passing incidentally.

The margin is cheap because the bound is answered from the covering index, not the table: on the synthetic 200k-row / 0.83 GB fixture, a 7-day window against the database is 1.087 s → 35.8 ms (30.4×), essentially unchanged from the exact-bound measurement.

Mixed-scale columns the sample misses (cubic P1, loader.rs:425) — accurate, not fixed, by design

This one is a real limitation and I am leaving it, so it is worth stating plainly rather than resolving quietly. Detecting mixed scales reliably needs a full-column scan, which is exactly the work this push-down exists to avoid — on the reported 33 GB install that scan is the bug being fixed. The LIMIT 8 sample is answered from the covering index and never touches data, which is what makes it affordable.

What bounds the risk: a uniformly non-millisecond column disables the push-down entirely; the day of margin now absorbs sub-day drift; and the payload comparison remains authoritative for every row that is returned. What remains exposed: a column with genuinely mixed scales, or drift beyond a day. Neither appears on any OpenCode build I can inspect — the local install shows a maximum delta of 0 across 600 rows, and OpenCode writes both values in the same statement. I would rather ship a documented sampling trade-off than either a full scan or a silent exclusion.

Already fixed before these comments landed

The other two cubic findings (loader.rs:315 raw-scan false positive, loader.rs:142 symlink traversal) were reported against cfed08d4 and were fixed in d72e2370 — same two issues CodeRabbit raised.

Verification for this push

  • cargo test --workspace 495 pass; clippy -D warnings and fmt --check clean
  • Parity against an origin/main-equivalent build on real data: 67 comparisons (55 date × timezone, 12 window shapes) — all byte-identical
  • Detected: list for an empty window still matches origin/main

@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.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread rust/adapters/opencode/src/loader.rs

@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 — widens the SQL push-down window by a day on each side so a message.time_created column that drifts from the payload's time.created costs a few extra rows to scan rather than silently excluding a row the report wanted. The per-row payload check remains the authoritative gate.

  • Widen the SQL push-down window with a day of marginDateWindow::widened_for_pushdown() adds MILLIS_PER_DAY on each bound before passing the window to prepare_message_query. The exact window is still applied per-row against the payload text in the loop.
  • Test that payload wins over a drifting columnpushdown_margin_keeps_rows_whose_column_drifts_from_the_payload places the column 26 hours after the payload date and verifies the row still surfaces with the payload's date.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 7ee95eee739b
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 337.4ms 2.98 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 319.6ms 3.15 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 119.5ms 8.42 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 106.0ms 9.49 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.7ms 29.1ms 0.95x 55.00 MiB 55.00 MiB 1.00x 0.06 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 26.5ms 24.0ms 1.10x 55.00 MiB 55.00 MiB 1.00x 0.06 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 26.8ms 23.7ms 1.13x 55.00 MiB 55.00 MiB 1.00x 0.03 MiB/s 0.04 MiB/s
codex session --offline --json 0.00 MiB 26.9ms 24.7ms 1.09x 55.00 MiB 55.25 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 365.5ms 359.4ms 1.02x 936.33 MiB 948.58 MiB 1.01x 2.75 GiB/s 2.80 GiB/s
codex --offline --json 1.01 GiB 120.7ms 121.4ms 0.99x 414.90 MiB 420.91 MiB 1.01x 8.34 GiB/s 8.29 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.70 KiB -0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.94 KiB +12.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

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 7ee95eee739b
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 340.2ms 2.96 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 308.4ms 3.27 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 116.8ms 8.62 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 95.5ms 10.54 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 native ccusage binary 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 31.7ms 4.9ms 6.50x 55.00 MiB 12.20 MiB 0.22x 0.05 MiB/s 0.32 MiB/s
claude session --offline --json 0.00 MiB 27.6ms 2.7ms 10.16x 55.00 MiB 12.21 MiB 0.22x 0.06 MiB/s 0.57 MiB/s
codex daily --offline --json 0.00 MiB 23.7ms 2.4ms 10.05x 55.00 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.36 MiB/s
codex session --offline --json 0.00 MiB 22.7ms 2.3ms 9.96x 54.75 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.38 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 372.1ms 304.7ms 1.22x 944.34 MiB 958.34 MiB 1.01x 2.71 GiB/s 3.30 GiB/s
codex --offline --json 1.01 GiB 117.1ms 95.1ms 1.23x 402.91 MiB 410.91 MiB 1.02x 8.59 GiB/s 10.58 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.70 KiB -0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.94 KiB +12.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

Copy link
Copy Markdown
Member Author

@coderabbitai review

The push-down changed since your last pass: 7ee95eee widens only the SQL bounds by a day (DateWindow::widened_for_pushdown) while the exact window still runs per row against the payload. Worth a look at whether the margin can affect which entries survive, and whether the time_created scale sampling is stated honestly about what it does and does not prove.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="$(fd -a -t f '^loader\.rs$' rust | rg '/adapter/opencode/loader\.rs$' | head -n1)"
date_utils="$(fd -a -t f '^date_utils\.rs$' rust | head -n1)"

printf '%s\n' '=== Commit and changed files ==='
git show --stat --oneline 7ee95eee -- 2>/dev/null || true

printf '%s\n' '=== OpenCode loader outline ==='
ast-grep outline "$target" --items all --view expanded 2>/dev/null || true

printf '%s\n' '=== Relevant loader symbols and contexts ==='
rg -n -C 10 \
  'DateWindow|widened_for_pushdown|time_created|scale|pushdown|date_range_bounds_ms|load_entries_from_database|payload|created' \
  "$target" || true

printf '%s\n' '=== Relevant tests ==='
rg -n -C 8 \
  'pushdown_margin_keeps_rows_whose_column_drifts_from_the_payload|scale|widened_for_pushdown|pushdown' \
  "$target" || true

printf '%s\n' '=== Date bounds implementation ==='
ast-grep outline "$date_utils" --items all --view expanded 2>/dev/null || true
rg -n -C 8 'date_range_bounds_ms|start_of_day_ms|checked_|saturat' "$date_utils" || true

Length of output: 60036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="$(fd -a -t f '^loader\.rs$' rust | rg '/adapter/opencode/loader\.rs$' | head -n1)"
parser="$(fd -a -t f '^parser\.rs$' rust | rg '/adapter/opencode/parser\.rs$' | head -n1)"

printf '%s\n' '=== Database loop (original versus pushed-down window use) ==='
sed -n '187,278p' "$target"

printf '%s\n' '=== Commit diff ==='
git show --format=fuller --find-renames 7ee95eee -- "$target"

printf '%s\n' '=== Payload parser timestamp/date construction ==='
ast-grep outline "$parser" --items all --view expanded 2>/dev/null || true
rg -n -C 8 'message_value_to_entry|time\.created|created|format_date|TimestampMs' "$parser" || true

printf '%s\n' '=== All scale-sampler references and explanatory wording ==='
rg -n -C 5 'time_created_is_millis|MIN_MILLIS_SCALE|same millisecond scale|Sampling a few|LIMIT 8' rust

Length of output: 22758


@ryoppippi Reviewed 7ee95eee.

The widened window is correctly confined to SQL selection. prepare_message_query(..., pushdown) receives the widened bounds, while the per-row check uses the original window and payload.time.created (Lines 219 and 258–263). Therefore, for rows whose payload timestamp can be extracted, the margin can only admit extra rows for parsing—it cannot make an out-of-range entry survive.

The remaining limitations are real but intentionally accepted:

  • A time_created/payload drift beyond the one-day margin can still cause a false exclusion.
  • time_created_is_millis inspects only LIMIT 8 values and tests only whether their maximum resembles milliseconds. It does not prove that every row is millisecond-scaled, nor that the column agrees with payload.time.created.

The commit message states both limitations candidly. I would only soften the source comments to match that reality—for example, Lines 204–205 should say the sample appears millisecond-scaled rather than “is known to hold the same millisecond scale as the payload,” and Lines 375–377 should qualify the drift protection as applying within the one-day margin.

No blocking correctness finding on the margin behavior.

Follow-up to a CodeRabbit review of 7ee95ee. The comments claimed more
than the code establishes: sampling eight values cannot show that the
whole column is millisecond-scaled, and matching scales say nothing about
matching values. Rename `time_created_is_millis` to
`time_created_looks_like_millis` and state the limits in place of the
guarantee, including that the margin absorbs drift only up to a day.

No behavior change.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai agreed on both wordings — fixed in 2e1af2d, comments only.

  • time_created_is_millistime_created_looks_like_millis, since the name was making the same overclaim as the comment. Its doc now says the sample proves nothing about the rows it did not read, and that matching scales say nothing about matching values.
  • The call site no longer says the column "is known to hold the same millisecond scale as the payload"; it says the sample still looks millisecond-scaled, which is why the payload check stays authoritative.
  • widened_for_pushdown now qualifies the protection: drift up to a day costs extra rows scanned, drift past a day is still excluded, and ruling that out would take the full-column scan this push-down exists to avoid.

Thanks for pushing on this — the code was already honest about the limits in the commit message, but the comments a future reader lands on were not.

Copilot AI review requested due to automatic review settings July 26, 2026 23:10

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Tip

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

@ryoppippi Thanks—those comment and naming updates accurately communicate the safety boundary: payload timestamps remain authoritative, sampling is only a heuristic, and the one-day margin prevents only bounded drift from becoming a SQL-level exclusion.

🐇✅


✏️ Learnings added
Learnt from: ryoppippi
URL: https://github.com/ccusage/ccusage/pull/1492

Timestamp: 2026-07-26T23:10:41.371Z
Learning: In `rust/crates/ccusage/src/adapter/opencode/loader.rs`, `time_created_looks_like_millis` samples only a limited set of SQLite `message.time_created` values; it does not establish the timestamp scale or value agreement for unexamined rows. SQL push-down bounds are widened by one day, but per-row payload `time.created` filtering remains authoritative; drift beyond one day can still exclude a row before payload validation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 2e1af2d98c0b
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 337.8ms 2.98 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 298.1ms 3.38 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 116.8ms 8.62 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 92.6ms 10.87 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 native ccusage binary 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 28.7ms 5.2ms 5.51x 55.25 MiB 12.20 MiB 0.22x 0.05 MiB/s 0.30 MiB/s
claude session --offline --json 0.00 MiB 26.2ms 3.3ms 8.04x 55.00 MiB 12.20 MiB 0.22x 0.06 MiB/s 0.47 MiB/s
codex daily --offline --json 0.00 MiB 24.8ms 2.3ms 10.78x 55.25 MiB 10.45 MiB 0.19x 0.03 MiB/s 0.37 MiB/s
codex session --offline --json 0.00 MiB 23.0ms 2.4ms 9.70x 55.25 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.36 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 352.7ms 316.3ms 1.12x 954.33 MiB 944.58 MiB 0.99x 2.85 GiB/s 3.18 GiB/s
codex --offline --json 1.01 GiB 111.3ms 91.2ms 1.22x 404.90 MiB 430.91 MiB 1.06x 9.05 GiB/s 11.04 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.70 KiB -0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.94 KiB +12.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

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 2e1af2d98c0b
Base SHA: df89eb712b75

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 338.8ms 2.97 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 303.0ms 3.32 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 115.1ms 8.75 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 107.6ms 9.35 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.6ms 24.3ms 1.22x 55.00 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.06 MiB/s
claude session --offline --json 0.00 MiB 29.0ms 24.5ms 1.18x 55.00 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 22.2ms 24.9ms 0.89x 55.00 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 23.4ms 24.1ms 0.97x 55.00 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.04 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 362.6ms 335.1ms 1.08x 958.34 MiB 940.58 MiB 0.98x 2.78 GiB/s 3.00 GiB/s
codex --offline --json 1.01 GiB 126.9ms 134.6ms 0.94x 410.91 MiB 434.91 MiB 1.06x 7.93 GiB/s 7.48 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.71 KiB 18.70 KiB -0.00 KiB 1.00x
installed native package binary 4141.94 KiB 4153.94 KiB +12.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.

…til-adapter

Resolves the workspace split from #1428, which moved the files this branch
touches into separate crates:

- `date_utils` now lives in `ccusage-core`. The window helpers added here
  (`date_within_range`, `date_range_bounds_ms`) are `pub` rather than
  `pub(crate)`, because the opencode adapter that calls them is its own
  crate now; `parse_compact_date` and `start_of_day_ms` stay private.
  `parse_digits` keeps main's private, overflow-checked version.
- `filter_loaded_entries_by_date` moved from the claude adapter to
  `ccusage-adapter-common`. Main's copy still inlined the date predicate,
  so it now calls the shared `date_within_range` instead — the point of
  collapsing the three copies in the first place.
- `opencode::has_data` follows the adapter crate's new `pub` surface.
Copilot AI review requested due to automatic review settings July 27, 2026 11:35

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 29d792b4ad78
Base SHA: 60f198460c7d

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 346.0ms 2.91 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 335.7ms 3.00 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 124.1ms 8.11 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 101.6ms 9.91 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 native ccusage binary 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 31.0ms 3.8ms 8.14x 55.00 MiB 12.45 MiB 0.23x 0.05 MiB/s 0.41 MiB/s
claude session --offline --json 0.00 MiB 24.1ms 5.0ms 4.78x 55.00 MiB 12.45 MiB 0.23x 0.06 MiB/s 0.31 MiB/s
codex daily --offline --json 0.00 MiB 24.2ms 2.3ms 10.43x 55.00 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.37 MiB/s
codex session --offline --json 0.00 MiB 25.6ms 2.4ms 10.83x 55.00 MiB 10.45 MiB 0.19x 0.03 MiB/s 0.36 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 378.6ms 323.2ms 1.17x 960.59 MiB 974.58 MiB 1.01x 2.66 GiB/s 3.12 GiB/s
codex --offline --json 1.01 GiB 120.2ms 93.4ms 1.29x 422.91 MiB 414.91 MiB 0.98x 8.38 GiB/s 10.77 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.70 KiB 18.70 KiB +0.00 KiB 1.00x
installed native package binary 4201.63 KiB 4210.13 KiB +8.50 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

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 29d792b4ad78
Base SHA: 60f198460c7d

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 334.9ms 3.01 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 320.9ms 3.14 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 119.1ms 8.45 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 92.9ms 10.84 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 26.9ms 24.5ms 1.10x 55.25 MiB 55.00 MiB 1.00x 0.06 MiB/s 0.06 MiB/s
claude session --offline --json 0.00 MiB 28.4ms 23.8ms 1.19x 55.00 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 25.5ms 24.3ms 1.05x 55.00 MiB 55.00 MiB 1.00x 0.03 MiB/s 0.04 MiB/s
codex session --offline --json 0.00 MiB 24.6ms 24.3ms 1.01x 55.25 MiB 55.00 MiB 1.00x 0.03 MiB/s 0.04 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 359.4ms 334.7ms 1.07x 934.58 MiB 938.59 MiB 1.00x 2.80 GiB/s 3.01 GiB/s
codex --offline --json 1.01 GiB 120.0ms 114.8ms 1.05x 408.91 MiB 414.91 MiB 1.01x 8.39 GiB/s 8.77 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.70 KiB 18.70 KiB +0.00 KiB 1.00x
installed native package binary 4201.63 KiB 4210.13 KiB +8.50 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.

@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.

🧹 Nitpick comments (1)
rust/crates/ccusage-adapter-all/src/loader.rs (1)

134-147: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a regression test for the OpenCode detected fallback.

This closure's rows.detected = rows.detected || opencode::has_data() fix addresses a real gap (OpenCode's loader pre-filters entries by the date window, so "non-empty entries" no longer proves source existence). None of the tests in this file's suite exercise this specific aggregate-wiring path (e.g., DB/JSON data exists entirely outside --since/--until but opencode should still land in detected_agents). Given opencode::load_entries is the only loader with this window-narrowing behavior, a small end-to-end test here would guard against regressions if load_summary_agent_rows's detection heuristic or the fallback ordering changes.

🤖 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-adapter-all/src/loader.rs` around lines 134 - 147, Add a
regression test in the loader test suite covering the aggregate path around the
OpenCode loader closure. Seed OpenCode data entirely outside the requested
since/until window, run the report-loading flow, and assert OpenCode remains in
detected_agents despite having no in-range entries. Exercise the rows.detected
fallback through opencode::has_data(), without changing the existing production
logic.
🤖 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.

Nitpick comments:
In `@rust/crates/ccusage-adapter-all/src/loader.rs`:
- Around line 134-147: Add a regression test in the loader test suite covering
the aggregate path around the OpenCode loader closure. Seed OpenCode data
entirely outside the requested since/until window, run the report-loading flow,
and assert OpenCode remains in detected_agents despite having no in-range
entries. Exercise the rows.detected fallback through opencode::has_data(),
without changing the existing production logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9dd8179-5dc1-4fb1-a7db-dc7abdb61e94

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1af2d and 29d792b.

📒 Files selected for processing (6)
  • rust/adapters/common/src/lib.rs
  • rust/adapters/opencode/src/lib.rs
  • rust/adapters/opencode/src/loader.rs
  • rust/crates/ccusage-adapter-all/src/loader.rs
  • rust/crates/ccusage-core/src/date_utils.rs
  • rust/crates/ccusage-core/src/summary.rs

@ryoppippi
ryoppippi merged commit 6d53c57 into main Jul 27, 2026
35 checks passed
@ryoppippi
ryoppippi deleted the fix/opencode-since-until-adapter branch July 27, 2026 12:38
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.

3 participants