Repository navigation
Description
Summary
ccusage claude session accepts --since / --until but the date window never
reaches the numbers. The flags only decide whether a session row is shown at
all; every row still reports the session's lifetime totals.
ccusage codex session scopes the same flags correctly, so the two providers
disagree under identical arguments.
This is silent and misleading: the row appears under a date filter, so the
figure reads as "usage in this window" when it is actually "usage since the
session was created". For a long-lived session the two differ by an order of
magnitude.
Three defects fall out of one root cause (details below).
Environment
- ccusage 20.0.20 (
npx ccusage@latest) - Windows 11, PowerShell 7.6.5
- Source inspected at
49ad744ccb0a1a1b167a2b6dfa6bbcc9ffe9423f
Defect A — claude session totals ignore the date window
Session ef018fb9, firstActivity 2026-08-04, lastActivity 2026-08-14.
Only --since varies; --timezone UTC throughout:
--since |
totalCost | totalTokens |
|---|---|---|
| 2026-01-01 | 1210.75 | 1,157,847,398 |
| 2026-08-13 | 1210.75 | 1,157,847,398 |
| 2026-08-14 | 1210.75 | 1,157,847,398 |
| (no filter) | 1210.75 | 1,157,847,398 |
--since 2026-08-14 excludes ten days of activity, yet nothing moves.
Not session-specific. Session 8fe7ee09, firstActivity 2026-08-12,
lastActivity 2026-08-14:
--since |
totalCost | totalTokens |
|---|---|---|
| 2026-01-01 | 620.11 | 780,678,594 |
| 2026-08-13 | 620.11 | 780,678,594 |
| 2026-08-14 | 620.11 | 780,678,594 |
| (no filter) | 620.11 | 780,678,594 |
The same test against codex session (session 019ee83f) scopes correctly:
--since |
costUSD | totalTokens |
|---|---|---|
| (no filter) | 401.15 | 391,238,184 |
| 2026-08-01 | 95.45 | 93,425,326 |
| 2026-08-15 | 12.29 | 11,827,686 |
Defect B — --until is off by one day
Same session, lastActivity = 2026-08-14T09:38:56.467Z, --timezone UTC:
| flags | result |
|---|---|
--until 2026-08-13 |
absent |
--until 2026-08-14 |
absent |
--until 2026-08-15 |
present, 1210.75 |
--since 2026-08-14 |
present, 1210.75 |
--since 2026-08-15 |
absent |
--until is documented as inclusive, so --until 2026-08-14 should keep a
session whose last activity is on 2026-08-14. The --since boundary is
correct; only --until is wrong.
Same class as #1390, which was fixed for the pi provider.
Defect C — --timezone does not apply to the session filter
The session filter compares against a UTC timestamp regardless of
--timezone, so the flag has no effect on which sessions are selected.
Root cause
All three follow from one filter block.
rust/crates/ccusage/src/commands/mod.rs:172-188:
if session_shared.since.is_some() || session_shared.until.is_some() {
rows.retain(|row| {
let date = row
.last_activity
.as_deref()
.unwrap_or_default()
.replace('-', "");
session_shared.since.as_ref().is_none_or(|since| &date >= since)
&& session_shared.until.as_ref().is_none_or(|until| &date <= until)
});
}This is the same shape as the daily filter in
rust/crates/ccusage-adapter-all/src/loader.rs:614-623:
fn filter_daily_summaries_by_date(rows: &mut Vec<UsageSummary>, shared: &SharedArgs) {
if shared.since.is_none() && shared.until.is_none() { return; }
rows.retain(|row| {
let date = row.date.as_deref().unwrap_or_default().replace('-', "");
shared.since.as_ref().is_none_or(|since| &date >= since)
&& shared.until.as_ref().is_none_or(|until| &date <= until)
});
}The only difference is the field read: row.date versus row.last_activity.
That single substitution breaks three assumptions at once.
A — wrong layer. In the daily report a row is a day, so retaining rows is
equivalent to filtering by date. In the session report a row is an entire
session, so retaining rows can only include or exclude a session whole. The
filter runs at commands/mod.rs:172, after aggregation completes at lines
168-171, and load_entries at line 152 applies no date bound — the Claude
adapter contains no reference to since or until anywhere
(rust/adapters/claude/src/). By the time the filter runs, the lifetime totals
are already computed.
B — wrong granularity. row.date is a plain YYYY-MM-DD, so
.replace('-', "") yields 20260814, which compares correctly against the
normalized bound. row.last_activity is a full RFC3339 timestamp
(ccusage-core/src/summary.rs:156 → format_rfc3339_millis), so the same
.replace yields 20260814T09:38:56.467Z. String-compared against 20260814:
"20260814T09:38:56.467Z" <= "20260814" -> false (dropped)
"20260814T09:38:56.467Z" <= "20260815" -> true (kept)
The trailing time component makes the value always greater than the bare date
for the same day, so --until on the boundary day always fails. The >= side
is unaffected, which is why only --until is off.
C — wrong timezone. format_rfc3339_millis
(ccusage-core/src/date_utils.rs:344) starts with timestamp.utc_parts() and
is UTC-only, whereas the daily path formats row.date through format_date
with shared.timezone. So the session filter silently ignores --timezone.
For contrast, the Codex adapter filters during parsing and aggregation
(rust/adapters/codex/src/aggregate.rs, parser.rs) rather than after, which
is why codex session scopes correctly.
Suggested fix
Push the date window down to entry loading / aggregation for the Claude path,
matching the Codex adapter. That fixes A, B and C together.
If a narrower fix is preferred, B and C can be addressed on their own by
truncating last_activity to a date and formatting it with shared.timezone
before comparing — but A would remain, and A is the one that produces wrong
numbers rather than a wrong row set.
Happy to test a patch.
Activity
pullfrog commentedon Aug 16, 2026
Issue #1609 is actionable and reproducible from the current code. The focused Claude path in
rust/crates/ccusage/src/commands/mod.rsloads and aggregates every entry, then filters completed rows usinglast_activityas an RFC3339 string; this cannot scope totals, rejects the inclusive--untilboundary day, and ignores the requested timezone. The unified Claude path inrust/crates/ccusage-adapter-all/src/loader.rshas the same post-aggregation behavior.Plan
rust/crates/ccusage/src/commands/mod.rsto apply the existingccusage_adapter_common::filter_loaded_entries_by_datehelper to loaded entries before session grouping. Remove the row-levellast_activitydate comparison. Apply the same entry filtering in the--idlookup path so focused session detail obeys the date window as well.rust/crates/ccusage-adapter-all/src/loader.rsto filter ClaudeLoadedEntryvalues beforesummarize_entry_sessions. Keep the separate behavior for other sources that use the shared session-summary helper unless their semantics are explicitly changed by a follow-up.--sinceand--untilchange token and cost totals, both boundary dates are inclusive, entries outside the window are excluded, and a non-UTC timezone determines the entry date. Cover both focused Claude session output and unified session rows so the two command surfaces stay consistent.--idlookup with a date window, including the no-in-range-entries result, and preserve deduplication, model breakdowns, session identity, and lifetime behavior when no bounds are supplied.docs/guide/session-reports.mdand the Claude session guide wording. Update the documented semantics to explain that date bounds select usage entries and scope session totals, then run the focused Rust tests plus the repository’s standard format, lint, and check recipes.Acceptance criteria
ccusage claude session --since/--untilreports totals calculated only from entries whose localized date is inside the inclusive window.--until YYYYMMDDincludes entries onYYYYMMDD, including the last activity boundary.--timezoneaffects date inclusion consistently with daily reports.ccusage sessionproduces the same Claude session totals for the same window.No implementation or tests were started in this planning run.
Task list (3/3 completed)
claude sessionignores --since/--until in totals (codex session does not) #1609 details and discussion@v0or keep the SHA fresh with Dependabot | Implement plan ➔ | View workflow run | via Pullfrog | UsingGPT Luna(free via Pullfrog for OSS) | 𝕏github-actions commentedon Aug 16, 2026
This issue was auto-closed. Issues from new contributors are auto-closed by default.
Maintainers review auto-closed issues and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md may not be reopened or receive a reply.
Keep the issue short, concrete, and written in your own voice.
If a maintainer replies
lgtmi, your future issues will stay open. If a maintainer replieslgtm, your future issues and PRs will stay open.See CONTRIBUTING.md.
ryoppippi commentedon Aug 27, 2026
@pullfrog Triage this issue using the repository standing issue-triage instructions. Inspect the full issue body, comments, current main branch, and related issues or pull requests. Choose exactly one priority label. If the issue is clearly unresolved and is priority:critical or priority:high with safe and well-scoped requirements, implement it and open a focused pull request. Otherwise, only triage it. Keep the explanation short and use simple English.
pullfrog commentedon Aug 27, 2026
Priority:
priority:high. This is a clear, reproducible bug in core usage totals: bounded Claude session reports can show lifetime cost and tokens, which is misleading for budgeting. The issue is unresolved onmain.I prepared commit
c7c305ewith the focused fix, regression tests, and documentation updates. The affected tests, clippy, and formatting checks pass, but the PR could not be opened becausegit pushwas denied with HTTP 403 forgithub-actions[bot]; the PR attempt then failed because the branch was not available remotely.@v0or keep the SHA fresh with Dependabot | View workflow run | via Pullfrog | UsingGPT Luna(free via Pullfrog for OSS) | 𝕏github-actions commentedon Aug 28, 2026
Pullfrog triage: priority:high
A maintainer explicitly requested an implementation attempt. Clear, reproducible, repository-scoped bug with a detailed root cause, expected behavior, and regression coverage scope. The issue remains unresolved, and the existing discussion indicates a safe focused fix was prepared but not published.
Decision: kept open; Pullfrog will attempt a focused implementation PR.
ryoppippi commentedon Aug 31, 2026
Historical audit: this discussion was auto-closed by the legacy contributor gate. That closure did not assess technical importance.
Audit result: resolved. A later merged change or the current
mainimplementation covers this request. This item is kept for history and does not need to be reopened.