From 856bd8fed2797457487a9b976d946243ab028ad0 Mon Sep 17 00:00:00 2001 From: cobra91 Date: Mon, 20 Jul 2026 09:05:41 +0200 Subject: [PATCH 1/3] docs: add audit-grade snapshot workflow for billing reconciliation Claude Code rewrites session JSONLs on resume/compact, which can drop earlier messages and make post-rewrite totals drift from actual usage (upstream bug, anthropics/claude-code#36583). better-ccusage reads the files faithfully and intentionally doesn't reconstruct a different history. Document the recommended workaround: snapshot the Claude data directory before resume/compact and point CLAUDE_CONFIG_DIR at the snapshot for audit-grade reports. Responds to the field-data point raised in #40 by @lizhuojunx86. --- docs/guide/directory-detection.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/guide/directory-detection.md b/docs/guide/directory-detection.md index 7bcbd4e7..4cbc4cb8 100644 --- a/docs/guide/directory-detection.md +++ b/docs/guide/directory-detection.md @@ -108,6 +108,25 @@ better-ccusage daily LOG_LEVEL=4 better-ccusage daily ``` +## Audit-grade reports (snapshot before resume/compact) + +Claude Code rewrites session JSONL files when you `resume` or `compact` a session, and in doing so can drop or rewrite earlier messages. Because better-ccusage reads those files faithfully, totals computed after a rewrite can drift from what you actually used earlier — historical usage that was compacted away is gone from the source. + +This is an [upstream Claude Code behavior](https://github.com/anthropics/claude-code/issues/36583), not a better-ccusage bug, and better-ccusage intentionally does not reconstruct a different history (no shadow ledger). For billing reconciliation or audit-grade totals, snapshot the Claude data directory before resuming/compacting long sessions, then point `CLAUDE_CONFIG_DIR` at the snapshot when you need an accurate-as-of-then report: + +```bash +# 1. Snapshot the current Claude data (do this before resume/compact) +SNAP="$HOME/claude-snapshots/$(date +%Y%m%d-%H%M%S)" +mkdir -p "$SNAP" +cp -r ~/.config/claude/projects "$SNAP/projects" + +# 2. Later, run reports against the frozen snapshot +export CLAUDE_CONFIG_DIR="$SNAP" +better-ccusage monthly --breakdown # totals as of the snapshot date +``` + +Keep snapshots around for as long as you need auditable numbers; they're plain JSONL files, so they compress well. See [#40](https://github.com/cobra91/better-ccusage/issues/40) for the original field report and repro. + ## Related Documentation - [Environment Variables](/guide/environment-variables) - Configure with CLAUDE_CONFIG_DIR From 2af0c5f62ff39e436308061d6088d7e42d3c967f Mon Sep 17 00:00:00 2001 From: cobra91 Date: Mon, 20 Jul 2026 09:11:19 +0200 Subject: [PATCH 2/3] docs: snapshot both Claude data roots in audit workflow Address CodeRabbit major review: the audit snippet only snapshotted ~/.config/claude/projects, but better-ccusage aggregates BOTH that and the legacy ~/.claude/projects/. Pointing CLAUDE_CONFIG_DIR at a single- root snapshot would silently omit legacy-session usage, invalidating the 'audit-grade totals' claim for users with data in both locations. Copy each existing root into its own snapshot subdir and pass them all as a comma-separated CLAUDE_CONFIG_DIR. Roots that don't exist are skipped (cp || rmdir), so single-root installs naturally get a single- path list. --- docs/guide/directory-detection.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/guide/directory-detection.md b/docs/guide/directory-detection.md index 4cbc4cb8..bd74ec47 100644 --- a/docs/guide/directory-detection.md +++ b/docs/guide/directory-detection.md @@ -112,20 +112,24 @@ LOG_LEVEL=4 better-ccusage daily Claude Code rewrites session JSONL files when you `resume` or `compact` a session, and in doing so can drop or rewrite earlier messages. Because better-ccusage reads those files faithfully, totals computed after a rewrite can drift from what you actually used earlier — historical usage that was compacted away is gone from the source. -This is an [upstream Claude Code behavior](https://github.com/anthropics/claude-code/issues/36583), not a better-ccusage bug, and better-ccusage intentionally does not reconstruct a different history (no shadow ledger). For billing reconciliation or audit-grade totals, snapshot the Claude data directory before resuming/compacting long sessions, then point `CLAUDE_CONFIG_DIR` at the snapshot when you need an accurate-as-of-then report: +This is an [upstream Claude Code behavior](https://github.com/anthropics/claude-code/issues/36583), not a better-ccusage bug, and better-ccusage intentionally does not reconstruct a different history (no shadow ledger). For billing reconciliation or audit-grade totals, snapshot the Claude data directory before resuming/compacting long sessions, then point `CLAUDE_CONFIG_DIR` at the snapshot when you need an accurate-as-of-then report. + +better-ccusage aggregates **both** Claude data roots by default — the new `~/.config/claude/projects/` and the legacy `~/.claude/projects/` (see [Default Directory Locations](#default-directory-locations)). A correct snapshot must capture each root that exists on your machine, then report against all of them via a comma-separated `CLAUDE_CONFIG_DIR`: ```bash -# 1. Snapshot the current Claude data (do this before resume/compact) +# 1. Snapshot every Claude data root that exists (do this before resume/compact) SNAP="$HOME/claude-snapshots/$(date +%Y%m%d-%H%M%S)" -mkdir -p "$SNAP" -cp -r ~/.config/claude/projects "$SNAP/projects" +mkdir -p "$SNAP/config" "$SNAP/legacy" +# Copy each existing root; ignore the ones you don't have. +cp -r ~/.config/claude/projects "$SNAP/config/projects" 2>/dev/null || rmdir "$SNAP/config" +cp -r ~/.claude/projects "$SNAP/legacy/projects" 2>/dev/null || rmdir "$SNAP/legacy" -# 2. Later, run reports against the frozen snapshot -export CLAUDE_CONFIG_DIR="$SNAP" +# 2. Later, run reports against the frozen snapshot (list every root you copied) +export CLAUDE_CONFIG_DIR="$SNAP/config,$SNAP/legacy" better-ccusage monthly --breakdown # totals as of the snapshot date ``` -Keep snapshots around for as long as you need auditable numbers; they're plain JSONL files, so they compress well. See [#40](https://github.com/cobra91/better-ccusage/issues/40) for the original field report and repro. +If you only have one root (e.g. a fresh install on `~/.config/claude`), the `CLAUDE_CONFIG_DIR` list naturally contains just that one path. Keep snapshots around for as long as you need auditable numbers; they're plain JSONL files, so they compress well. See [#40](https://github.com/cobra91/better-ccusage/issues/40) for the original field report and repro. ## Related Documentation From dc9212e0d68e0887c6dac51a23755db36e1f8685 Mon Sep 17 00:00:00 2001 From: cobra91 Date: Mon, 20 Jul 2026 13:19:16 +0200 Subject: [PATCH 3/3] docs: accurate single-root note for audit snapshot workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address kilo-code review: the previous wording claimed the CLAUDE_CONFIG_DIR list 'naturally contains just that one path' for single-root installs, which is inaccurate — the env var still holds both literal paths; it's better-ccusage that silently skips the missing one at runtime. Reword to describe the actual mechanism (rmdir removes the empty stub, the stale entry simply won't resolve) so an auditor running 'echo $CLAUDE_CONFIG_DIR' isn't misled by a ghost path. --- docs/guide/directory-detection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/directory-detection.md b/docs/guide/directory-detection.md index bd74ec47..e381483d 100644 --- a/docs/guide/directory-detection.md +++ b/docs/guide/directory-detection.md @@ -129,7 +129,7 @@ export CLAUDE_CONFIG_DIR="$SNAP/config,$SNAP/legacy" better-ccusage monthly --breakdown # totals as of the snapshot date ``` -If you only have one root (e.g. a fresh install on `~/.config/claude`), the `CLAUDE_CONFIG_DIR` list naturally contains just that one path. Keep snapshots around for as long as you need auditable numbers; they're plain JSONL files, so they compress well. See [#40](https://github.com/cobra91/better-ccusage/issues/40) for the original field report and repro. +If you only have one root (e.g. a fresh install on `~/.config/claude`), `rmdir` removes the empty stub for the missing root and better-ccusage silently skips it, so the report still runs against the one root you copied (the stale entry in `CLAUDE_CONFIG_DIR` simply won't resolve). Keep snapshots around for as long as you need auditable numbers; they're plain JSONL files, so they compress well. See [#40](https://github.com/cobra91/better-ccusage/issues/40) for the original field report and repro. ## Related Documentation