[PR #8590] feat(auth): OAuth Marathon - multi-account credential rotation #12801

Open
opened 2026-02-16 18:17:41 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/8590

State: open
Merged: No


OAuth Marathon 🏃

Keep running when you hit the wall. This PR adds automatic credential rotation for OAuth providers - when one account hits rate limits or auth errors, opencode seamlessly switches to your next available credential within the same provider.

Note: This rotates between multiple accounts within the same provider (e.g., two OpenAI logins), not between different providers.

Closes #8591

Works for all OAuth providers — both core providers and plugins.

For plugin authors: Plugins that manage their own multi-account pools internally must register each account via Auth.addOAuth() to benefit from this feature.

The Problem

Using OAuth providers with personal subscriptions often means hitting rate limits mid-session. Currently, when this happens, your request fails and you're stuck waiting.

The Solution

Register multiple OAuth accounts for the same provider, and opencode will automatically:

  • Rotate on 429 - Rate limited? Next credential steps in
  • Retry on 401/403 - Force token refresh, failover if still failing
  • Recover from network errors - Transient failures don't stop the run
  • Track health - Cooldown periods prevent hammering exhausted credentials
  • All exhausted - Returns the last error response gracefully

How to Add Multiple Accounts

Run opencode auth login multiple times for the same provider:

opencode auth login   # Login with first account
opencode auth login   # Login with second account (adds, doesn't replace)

Architecture Overview

flowchart TD
    A[Provider.getSDK] --> B[createOAuthRotatingFetch]
    B --> C{fetchFn}
    C -->|429 Rate Limit| D[moveToBack + notifyFailover]
    C -->|401/403 Auth| E[markAccessExpired + retry]
    C -->|Network Error| F[recordOutcome + notifyFailover]
    C -->|200 OK| G[recordOutcome success]
    D --> H[Try Next Credential]
    E -->|Still fails| H
    F --> H
    H --> C

Demo

OpenAI Account 1 → 429 Rate Limited
               ↓ (automatic)
OpenAI Account 2 → 200 OK ✅
               ↓
Toast: "Rate limited on openai. Switching OAuth credential..."

Configuration (Optional)

Per-provider settings in opencode.json. Sensible defaults are used if omitted:

{
  "provider": {
    "openai": {
      "oauth": {
        "maxAttempts": 3,                // default: number of accounts
        "rateLimitCooldownMs": 60000,    // default: 30000
        "authFailureCooldownMs": 300000, // default: 300000
        "toastDurationMs": 5000          // default: 8000
      }
    }
  }
}

Changes

  • src/auth/rotating-fetch.ts - Core rotation logic
  • src/auth/context.ts - AsyncLocalStorage for request scoping
  • src/auth/credential-manager.ts - Toast notifications
  • src/auth/index.ts - OAuth pool management & persistence
  • src/config/config.ts - New oauth config schema
  • test/auth/oauth-rotation.test.ts - 10 test cases

Verification

How I tested:

  • 10 unit tests covering core scenarios and edge cases
  • All tests pass

Test Coverage

  • 429 rotation with Retry-After header
  • Retry-After HTTP date format support
  • 401/403 with forced refresh recovery
  • 401/403 failover when refresh fails
  • Sticky credential until rate limited
  • Non-replayable bodies (streaming)
  • All credentials exhausted
  • Network error failover
  • Request.clone failure handling
  • Refresh token record matching
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/8590 **State:** open **Merged:** No --- ## OAuth Marathon 🏃 Keep running when you hit the wall. This PR adds automatic credential rotation for OAuth providers - when one account hits rate limits or auth errors, opencode seamlessly switches to your next available credential **within the same provider**. > **Note:** This rotates between multiple accounts *within* the same provider (e.g., two OpenAI logins), not between different providers. Closes #8591 Works for **all OAuth providers** — both core providers and plugins. > **For plugin authors:** Plugins that manage their own multi-account pools internally must register each account via `Auth.addOAuth()` to benefit from this feature. ### The Problem Using OAuth providers with personal subscriptions often means hitting rate limits mid-session. Currently, when this happens, your request fails and you're stuck waiting. ### The Solution Register multiple OAuth accounts for the same provider, and opencode will automatically: - **Rotate on 429** - Rate limited? Next credential steps in - **Retry on 401/403** - Force token refresh, failover if still failing - **Recover from network errors** - Transient failures don't stop the run - **Track health** - Cooldown periods prevent hammering exhausted credentials - **All exhausted** - Returns the last error response gracefully ### How to Add Multiple Accounts Run `opencode auth login` multiple times for the same provider: ```bash opencode auth login # Login with first account opencode auth login # Login with second account (adds, doesn't replace) ``` ### Architecture Overview ```mermaid flowchart TD A[Provider.getSDK] --> B[createOAuthRotatingFetch] B --> C{fetchFn} C -->|429 Rate Limit| D[moveToBack + notifyFailover] C -->|401/403 Auth| E[markAccessExpired + retry] C -->|Network Error| F[recordOutcome + notifyFailover] C -->|200 OK| G[recordOutcome success] D --> H[Try Next Credential] E -->|Still fails| H F --> H H --> C ``` ### Demo ``` OpenAI Account 1 → 429 Rate Limited ↓ (automatic) OpenAI Account 2 → 200 OK ✅ ↓ Toast: "Rate limited on openai. Switching OAuth credential..." ``` ### Configuration (Optional) Per-provider settings in `opencode.json`. Sensible defaults are used if omitted: ```json { "provider": { "openai": { "oauth": { "maxAttempts": 3, // default: number of accounts "rateLimitCooldownMs": 60000, // default: 30000 "authFailureCooldownMs": 300000, // default: 300000 "toastDurationMs": 5000 // default: 8000 } } } } ``` ### Changes - `src/auth/rotating-fetch.ts` - Core rotation logic - `src/auth/context.ts` - AsyncLocalStorage for request scoping - `src/auth/credential-manager.ts` - Toast notifications - `src/auth/index.ts` - OAuth pool management & persistence - `src/config/config.ts` - New `oauth` config schema - `test/auth/oauth-rotation.test.ts` - 10 test cases ### Verification **How I tested:** - 10 unit tests covering core scenarios and edge cases - All tests pass ### Test Coverage - ✅ 429 rotation with Retry-After header - ✅ Retry-After HTTP date format support - ✅ 401/403 with forced refresh recovery - ✅ 401/403 failover when refresh fails - ✅ Sticky credential until rate limited - ✅ Non-replayable bodies (streaming) - ✅ All credentials exhausted - ✅ Network error failover - ✅ Request.clone failure handling - ✅ Refresh token record matching
yindo added the pull-request label 2026-02-16 18:17:41 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#12801