fix: scope session-equivalent forecast history by account - #276
Open
zaszlo wants to merge 1 commit into
Open
Conversation
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.
Problem
Session-equivalent forecast history is keyed by
provider_idalone, so it is shared across every account on that provider.SessionEquivalentHistoryStoreandLastFullSessionEstimateStoreare bothHashMap<String, _>keyed by provider, andbridge.rspassesid.cli_name()— the literal string"codex"or"claude"— with no account discriminator:Fetch itself is single-account, so nothing mixes concurrently. The problem is switching:
switch_active_account()changes which account is fetched, while the ring buffer — sizedsample_limit * 8, minimum 24 entries — still holds the previous account's observations.After a switch the burn median is computed from a mixture of two accounts' samples until the old entries age out. Moving between plans of different sizes produces a silently wrong "estimated windows to exhaust" for the following several windows, with nothing in the UI signalling the number is unreliable.
The existing source comment already anticipates the correct key:
This is the same class of fix as #203, which scoped quota notifications by account identity. That change never reached forecast history.
Change
Introduce
ForecastScope { provider_id, account_key }and key both stores by it.SessionEquivalentHistoryStore.by_provider→by_scopeLastFullSessionEstimateStore.by_provider→by_scoperecord_provider_windows(),forecast_for_provider(),remember_full_session_estimate(),record(),histories()take the account discriminatorSourcing the discriminator
Claude publishes
account_email, but Codex publishes neither email nor organization — under the ADR 0003 ambient/managed lane split its only stable discriminator is the managed token-account id. Keying off the email alone would have left this bug live for Codex, which is the provider with first-class multi-account support.So the identity is resolved where it actually lives and threaded down:
spawn_provider_refreshes(hasinputs.token_accounts) →refresh_provider→fetch_provider_snapshot→from_fetch_resultA new
forecast_account_key()deliberately mirrorsquota_notification_account_identityprecedence —token-account:{uuid}→ email →org:{org}. It is a separate function only because that one consumes an already-builtProviderUsageSnapshotwhile the forecast needs the key while the snapshot is being built.Behaviour for single-account users is unchanged: one account means one scope.
Notes
UsageSnapshot. A comment marks that it should be hashed rather than written in the clear if this history is later persisted to disk.account_key: None, which is its own bucket rather than a catch-all shared with identified accounts.Tests
history_is_isolated_per_account— two accounts on one provider; asserts neither account's samples appear in the other's history and that theNonebucket stays empty.forecast_account_key_matches_notification_identity— pinsforecast_account_keytoquota_notification_account_identityacross all 8 combinations of email/org/token, so the two subsystems can never see one account as two identities. If they diverged, an account's burn history would split and silently halve the sample count behind every forecast.history_ring_retains_latest_samplesupdated for the new signature.Verification
Note on
cli::tty_runner::tests::test_run_sends_script_through_pty: it failed on onefull-suite run here and passed on others. I checked it against untouched
main(
116af24b), where it also fails under full-suite parallel load, so it is pre-existingflakiness in this environment and unrelated to this change.
No CUA Driver proof included: this changes no UI, tray, or Settings surface. The forecast line in
MenuCardDetailsrenders exactly as before for a single-account user.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.