feat: add multi-account support with automatic rate limit rotation
Summary
- Multi-account authentication: Users can authenticate up to 10 ChatGPT accounts during initial OAuth flow
- Automatic rate limit rotation: When a 429 response is received, automatically switches to the next available account
-
CLI management tool: New
codex-accountscommand for add/list/remove operations outside OAuth flow
Problem
Heavy users of the Codex plugin frequently hit ChatGPT's rate limits, causing interruptions. Users with multiple ChatGPT subscriptions (personal, work, etc.) have no way to leverage them for increased throughput.
Solution
This PR implements multi-account management that:
- Prompts users to add additional accounts during initial authentication
- Persists account metadata separately for runtime state tracking
- Automatically rotates to an available account when rate limits are hit
- Provides a CLI tool for managing accounts after initial setup
Changes
| File | Description |
|---|---|
lib/accounts/manager.ts |
New - AccountManager class with round-robin rotation, rate limit tracking |
lib/accounts/storage.ts |
New - Persistent storage to ~/.opencode/openai-codex-accounts.json |
lib/accounts/cli.ts |
New - CLI prompt helper for multi-account setup |
scripts/manage-accounts.js |
New - Standalone CLI tool (codex-accounts add/list/remove) |
lib/types.ts |
Added types: ManagedAccount, AccountStorage, OAuthAuthDetails, RateLimitState |
lib/constants.ts |
Added MAX_ACCOUNTS = 10 |
lib/auth/auth.ts |
Added isOAuthAuth() and accessTokenExpired() helpers |
index.ts |
Refactored OAuth flow for multi-account, automatic 429 rotation |
test/accounts.test.ts |
New - Comprehensive test coverage for account management |
package.json |
Added codex-accounts bin entry |
How It Works
Authentication Flow
opencode auth login
→ Complete first account OAuth
→ "You have 1 account(s). Add another? [y/N]: y"
→ Complete second account OAuth
→ ... repeat up to 10 accounts
Refresh Token Format
Multiple accounts are encoded in the refresh token field:
refresh1|accountId1||refresh2|accountId2||refresh3|accountId3
Rate Limit Handling
- Request sent using current account
- If 429 received, parse
Retry-Afterheader - Mark current account as rate-limited with reset time
- Rotate to next available account
- Persist state to storage file
CLI Management
# List all configured accounts
codex-accounts list
# Add another account
codex-accounts add
# Remove account by index
codex-accounts remove 1
Storage Locations
| Platform | Path |
|---|---|
| Linux | ~/.local/share/opencode/openai-codex-accounts.json |
| macOS | ~/.local/share/opencode/openai-codex-accounts.json |
| Windows | %APPDATA%/opencode/openai-codex-accounts.json |
Testing
Added comprehensive test suite in test/accounts.test.ts covering:
- Multi-account refresh token parsing/formatting
- AccountManager initialization from storage vs refresh string
- Rate limit tracking and account rotation
- Add/remove account operations
- Auth details conversion
Backwards Compatibility
- Single-account users are unaffected (no prompt to add more unless they want to)
- Existing refresh tokens work as-is (parsed as single account)
- Storage file is only created when multiple accounts are configured
Because of expiring refresh tokens, the rotation could happen in certain intervals, e.g. 5% weekly used, as an idea
Because of expiring refresh tokens, the rotation could happen in certain intervals, e.g. 5% weekly used, as an idea
Keep in mind that every time we get a 429 it will switch to the next account. I expect pretty much a sort of round robin distribution and "organic" refreshes.
here u go i hope it helps
https://github.com/ndycode/opencode-openai-codex-auth-multi
Because of expiring refresh tokens, the rotation could happen in certain intervals, e.g. 5% weekly used, as an idea
Keep in mind that every time we get a 429 it will switch to the next account. I expect pretty much a sort of round robin distribution and "organic" refreshes.
Yes but sometimes you leave the other accounts bare for whatever reason. My suggestion is not a good one, in retrospect. If a refresh token is expired for whatever reason, a new JWT should be created aka it needs to prompt the user to log in again, IMHO, if not already the case.
He's taking his time. @ndycode thanks!