--provider <x> leaks claude spend into every live-parse panel
What happens
codeburn report --provider cursor reports a correct cursor-only headline, but the panels underneath it carry claude spend. By Model lists Anthropic models under a cursor filter:
$ codeburn report -p all --provider cursor
│ CodeBurn 6 Months │
│ €3.79 cost 132 calls 50 sessions │ <- cursor only, correct
...
│ By Model │
│ ██████████ Fable 5 €1402.89 98.4% 2767 │ <- claude
│ ███░░░░░░░ Opus 5 €409.13 99.0% 1493 │ <- claude
│ ██░░░░░░░░ Opus 4.8 €224.34 94.4% 588 │ <- claude
│ ░░░░░░░░░░ Cursor (auto) ~€2.40 0.0% 44 │
Same run via --format json:
|
value |
overview.cost |
3.79 |
daily summed |
3.79 |
projects summed |
2038.99 |
models summed |
2038.99 |
activities summed |
2038.99 |
--provider codex produces byte-identical claude figures, so the leaked amount is independent of which provider you ask for.
The two leaked project buckets on my machine (-Users-husamsoboh, -Users-husamsoboh-Downloads, 11 sessions total) match exactly the 11 entries in my session cache that carry prLinks:
$ jq '[.providers.claude.files[] | select((.prLinks//[])|length>0)] | length' \
~/.cache/codeburn/session-cache.v7.json
11
Root cause
Claude is scanned through scanProjectDirs rather than parseProviderSources, and that call has no provider-filter guard (src/parser.ts:3739-3748).
On a --provider cursor run, discoverAllSessions correctly returns no claude sources (src/providers/index.ts:260-262), so claudeDirs is empty. But scanProjectDirs still runs. Inside it, allDiscoveredFiles stays empty while section holds the entire cached claude history, so the orphan pass treats every cached file as no longer discovered (src/parser.ts:1969-1976):
for (const [filePath, cached] of Object.entries(section.files)) {
if (allDiscoveredFiles.has(filePath)) continue
if (!readOnly && !cached.prLinks?.length) continue // only gate
...
unchangedFiles.push({ filePath, dirName, cached })
}
That gate was written for genuinely deleted transcripts. It cannot distinguish "the file is gone" from "the file was not scanned because --provider excluded it this run".
- Write mode: every PR-bearing claude session leaks.
- Read-only mode (
readOnly = true, taken whenever the cache refresh lock is unavailable, e.g. while the menubar app is running): the gate is bypassed entirely and every cached claude file leaks.
Every other provider already has this guard, a few dozen lines below in the same function (src/parser.ts:3772-3775):
// Skip if filtered to a different provider
if (providerFilter && providerFilter !== 'all' && providerFilter !== providerName) continue
Note that the paired eviction block does carry a dirs.length > 0 guard (src/parser.ts:2156), so nothing is wrongly deleted from the cache. The asymmetry is only in the surfacing loop.
The headline escapes the bug because it comes from the provider-sliced daily cache (sliceDayToProvider), while the panels are built from the live parse. That is why the two cannot be reconciled, similar in shape to #865 but on the provider axis instead of the project axis.
Affected surfaces
Anything that passes a specific non-claude provider into parseAllSessions, buildDurablePeriod, or buildMenubarPayloadForRange: report, today, month, compare, audit, models, sessions, yield, spend, export, web, and status --format menubar-json. plan usage tracking is the one with real consequences: a plan scoped to a non-claude provider counts leaked claude spend against that plan's budget.
--provider all and --provider claude are unaffected. mcp and the sharing paths are unaffected because they hardcode provider: 'all'.
Environment
- codeburn 0.9.19, macOS 26.5.2, Node 22.23
- providers present: claude, cursor, codex, and others
I have a fix ready (a provider guard on the claude scan, plus regression coverage) and will open a PR against this issue unless you would rather take a different approach.
--provider <x>leaks claude spend into every live-parse panelWhat happens
codeburn report --provider cursorreports a correct cursor-only headline, but the panels underneath it carry claude spend.By Modellists Anthropic models under a cursor filter:Same run via
--format json:overview.costdailysummedprojectssummedmodelssummedactivitiessummed--provider codexproduces byte-identical claude figures, so the leaked amount is independent of which provider you ask for.The two leaked project buckets on my machine (
-Users-husamsoboh,-Users-husamsoboh-Downloads, 11 sessions total) match exactly the 11 entries in my session cache that carryprLinks:Root cause
Claude is scanned through
scanProjectDirsrather thanparseProviderSources, and that call has no provider-filter guard (src/parser.ts:3739-3748).On a
--provider cursorrun,discoverAllSessionscorrectly returns no claude sources (src/providers/index.ts:260-262), soclaudeDirsis empty. ButscanProjectDirsstill runs. Inside it,allDiscoveredFilesstays empty whilesectionholds the entire cached claude history, so the orphan pass treats every cached file as no longer discovered (src/parser.ts:1969-1976):That gate was written for genuinely deleted transcripts. It cannot distinguish "the file is gone" from "the file was not scanned because
--providerexcluded it this run".readOnly = true, taken whenever the cache refresh lock is unavailable, e.g. while the menubar app is running): the gate is bypassed entirely and every cached claude file leaks.Every other provider already has this guard, a few dozen lines below in the same function (
src/parser.ts:3772-3775):Note that the paired eviction block does carry a
dirs.length > 0guard (src/parser.ts:2156), so nothing is wrongly deleted from the cache. The asymmetry is only in the surfacing loop.The headline escapes the bug because it comes from the provider-sliced daily cache (
sliceDayToProvider), while the panels are built from the live parse. That is why the two cannot be reconciled, similar in shape to #865 but on the provider axis instead of the project axis.Affected surfaces
Anything that passes a specific non-claude provider into
parseAllSessions,buildDurablePeriod, orbuildMenubarPayloadForRange:report,today,month,compare,audit,models,sessions,yield,spend,export,web, andstatus --format menubar-json.planusage tracking is the one with real consequences: a plan scoped to a non-claude provider counts leaked claude spend against that plan's budget.--provider alland--provider claudeare unaffected.mcpand the sharing paths are unaffected because they hardcodeprovider: 'all'.Environment
I have a fix ready (a provider guard on the claude scan, plus regression coverage) and will open a PR against this issue unless you would rather take a different approach.