auth: multi-account OAuth subscription failover #5754
Closed
+840
−48
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.
Implements the missing piece of #5391 for OAuth-based subscription providers: store multiple OAuth accounts per provider and automatically fail over within the same user request when an account is rate limited or its session expires.
This PR is intentionally scoped to the core value (no TUI, no model discovery, no vault CLI).
Why
Today an OAuth login is effectively single-account-per-provider. When that account hits
429or expires, the request fails and disrupts the user.Goal: allow multiple “subscription” accounts (OAuth, not API-billing) and make inference resilient by retrying with the next account automatically.
Storage
Bun.secrets(serviceopencode); disk stores only non-secret OAuth metadata (labels, pool order, cooldowns, last status).auth.json(unchanged) to avoid overlapping with [FEATURE]: Allow storage of secrets in system credential store. #4318’s broader keyring work.Existing
auth.jsonis migrated to a v2 format on first read; OAuth secrets are moved into the keychain.What changed
429→ Retry-After-aware cooldown + rotate to next account and retry in the same request401/403→ force a refresh (by clearing access/expires) and retry once; if still unauthorized, rotateAuth.set()updates the correct OAuth record by matching therefreshtoken (works with/auth/:providerID).opencode auth loginOAuth paths now add accounts instead of overwriting.Review order
feat(auth): keychain-backed oauth records(store + migration)feat(auth): rotate oauth subscriptions on 429/401(rotation + provider integration + CLI)test(auth): cover oauth failoverfix(auth): keep api tokens in file, update oauth selection(avoid [FEATURE]: Allow storage of secrets in system credential store. #4318 overlap + robust refresh targeting)test(auth): auth.set selects oauth record by refreshfix(auth): only require keychain for oauth migrationtest(auth): isolate oauth rotation provider idHow to test
opencode auth logintwice for the same OAuth provider (e.g. Anthropic “Claude Pro/Max”, Copilot) to create multiple subscription accounts.packages/opencode:bun test test/auth/oauth-rotation.test.tsNotes