fix(menubar): apply --project/--exclude to the provider list on carried days - #889
Merged
iamtoruk merged 1 commit intoAug 4, 2026
Conversation
…ed days The provider list rebuilt its own day set straight from the daily cache, unioning unfiltered historical days with an already-filtered today. Per-provider costs therefore counted every carried day whole while today honoured the name filters, so the list could not be reconciled with the headline or the By Project panel. getagentseal#864 fixed the headline and left this deliberately untouched. Reuse durable.days, which is the same union the headline is built from, already narrowed by range, day selection and project filter. That is what the comment above the buildDurablePeriod call already promised this section would do. Providers whose entire spend is excluded do not vanish from the list: the installed-but-zero backfill below still adds them at cost 0.
iamtoruk
approved these changes
Aug 4, 2026
iamtoruk
left a comment
Member
There was a problem hiding this comment.
Reviewed on the merged tree, closes the #865 gap that #864 left. Real money-accuracy fix: on carried days the menubar provider list was built from an unfiltered cache slice, so under --project/--exclude per-provider costs counted every carried day whole and could not reconcile with the headline or the By Project panel.
- The fix reuses cacheDaysForPeriod (durable.days), the same cache-plus-today union the headline is built from, already narrowed by range, day selection and the project filter. Verified non-null in this branch: it is the !isClaudeConfigScoped path, which forces the !effectivelyScoped assignment.
- The installed-but-zero backfill below still adds any provider with discoverable sources at cost 0, so narrowing the totals does not drop providers from the list (the concern #865 raised).
- Mutation-checked: reverting to the unfiltered union fails the new "slices the provider list by the project filter so it reconciles with the headline" test.
- tsc clean; the new project-filter test passes; the pre-existing cli-durable-totals single-provider parity failure is on a different code path and reproduces on main (unrelated to this change).
Good to merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #865, which was closed when #864 (2de4d10) fixed the Overview headline via
sliceDayToProject. This PR closes the adjacent gap that issue documented and #864 deliberately left alone: the menubar provider list still ignores--project/--excludeon carried days. Verified present on currentmain(44a94f5,unfilteredProviderDaysat usage-aggregator.ts:637).The bug
buildMenubarPayloadForRangebuilt the provider list from its own day set rather than the one the headline came from:Historical days came straight out of the cache unfiltered, while
getTodayAllDays()was already name-filtered. Under--project/--exclude, per-provider costs therefore counted every carried day whole and could not be reconciled with either the headline or the By Project panel.On my own data,
status --format menubar-json --period 30days --project codeburn:The fix
Reuse
durable.days, captured a few lines above ascacheDaysForPeriod. It is the same cache-plus-today union the headline is built from, already narrowed by range, day selection and the project filter. The comment above thebuildDurablePeriodcall already stated that this section reuses that slice, so this makes the code match what it claims:cacheDaysForPeriodis non-null in this branch: reaching it implies!isClaudeConfigScoped, which forces the!effectivelyScopedpath that assigns it.On the concern that raised this as a policy question
#865 held this back because "narrowing it also changes which providers appear in the list at all". In practice it does not. The installed-but-zero backfill immediately below still adds any provider with discoverable sources at
cost: 0:Confirmed on real data: under
--project codeburn, the providers whose entire spend is excluded still appear, at 0.00.That is a weaker disappearance than the By Project panel already allows, since
buildTopProjectsdrops zero-cost projects outright.Pre-v15 days with no per-project split keep #864's accepted policy for free, because the slicing happens inside
sliceDayToProjectrather than here.Tests
Two cases added to
tests/project-filter-durable-totals.test.ts, reusing that file's fixture style. A newcarriedDayTwoProvidershelper builds a carried day whose providers own disjoint projects (claude onkeep-me, codex ondrop-me), so filteringdrop-meout must take codex's whole contribution with it.slices the provider list by the project filter so it reconciles with the headlineasserts the provider costs sum to the headline and that codex drops to zero. Without the fix it fails withexpected 100 to be close to 30, received difference is 70.leaves the provider list untouched when no project filter is givenlocks the unfiltered path.Verified unfiltered on real data as well: same provider set, identical costs to the cent, no rows dropped.
tsc --noEmitclean. Full suite passes apart from failures that reproduce identically on unmodifiedmainin my environment (tests/cache-refresh-lock.test.ts, which is flaky under parallel load and passes in isolation, and the twotests/parser.test.tsdurable-orphan cases that fail on their first parse withexpected +0 to be 200).