Repository navigation
[Feature]: complete the OAuth account-pool lifecycle — session affinity, 401/403 rotation, pool health, and stable reset-credit identity #3375
Description
CLI truthfulness slice landed; pool epic remains open
#3797 (a92835dbc6) makes inert generic-pool thresholds explicit in CLI status. This does not complete the broader account-pool lifecycle epic. Existing reset-operation identity work must not be counted as unimplemented solely from the older summary.
Verified against dev 5759d9ea2f1e7281cdc01eb9628f2e0a123fb59c. The attribution record was added in #3811.
Area
Authentication and account pool
What are you trying to accomplish?
Run OpenCodex against a pool of OAuth/subscription accounts and have it pick the right account, keep a conversation on that account, and recover from per-account failures without operator intervention.
This consolidates the surviving scope from four reports whose first tranche already landed. Each is closed individually and absorbed here so the remaining work stays visible in one place:
- [Feature]: Generic OAuth account-pool failover with session affinity and quota-aware selection #695 (@luwei1990) — generic OAuth account-pool failover with session affinity and quota-aware selection
- [Feature] Google Antigravity multi-account management, pool health and quota UX #1062 (@agentHits) — Google Antigravity multi-account management, pool health and quota UX
- Proposal: Pin the re-initialization window of zero-usage Codex accounts with a one-shot shadow request #1977 (@dbc-hbin) — pinning the re-initialization window of zero-usage Codex accounts
- [Feature]: make manual reset-credit retries use durable operation identity #2275 (@luvs01) — durable operation identity for manual reset-credit retries
What prevents this today?
Partial implementations exist and work, but four concrete gaps remain.
Session affinity is not reusable. src/oauth/generic-account-failover.ts implements 429 cooldown, rotation, and quota-ranked initial selection (landed in 816f3a1). It does not keep a multi-turn conversation pinned to the account that started it, so a long session can drift across accounts mid-conversation.
Failover classes are too narrow. Only 429 rotates. A 401 and a provider-classified 403 are terminal for the pool even when another account in the pool would succeed.
Pool-level health and attribution are missing. Per-account quota is surfaced (src/providers/quota.ts, gui/src/components/provider-workspace/ProviderAuthPanel.tsx), but there is no aggregate pool capacity view and no per-account attribution of usage, so an operator cannot see which account is carrying the load or why a selection was made.
Manual reset-credit retries still mint a new identity per request. The durable ledger landed (src/codex/reset-credit-operation-ledger.ts, 7c68768), but the manual consume endpoint generates a fresh UUID per call at src/codex/auth-api.ts, so a retried manual reset is not recognized as the same logical operation the ledger was built to track.
Zero-usage accounts have no durable warmup scheduling. src/codex/warmup.ts provides a manual warmup primitive only; there is no one-shot scheduled shadow request to pin an account's re-initialization window.
What should OpenCodex do?
- Keep a conversation on its selected account for the life of the session, with an explicit, configurable release condition, and fall over only when that account genuinely cannot serve.
- Treat 401 and provider-classified 403 as rotatable within the pool, with the credential marked unhealthy rather than the request failed.
- Expose aggregate pool health — capacity, per-account quota state, and which account served a request — in the dashboard and in request history.
- Accept a caller-supplied stable operation ID on the manual reset-credit endpoint and CLI, and open/settle that exact ID in the existing ledger so a retry is idempotent.
- Allow a durable one-shot warmup to be scheduled for a zero-usage account so its window starts predictably.
Example usage or interface
// config.json
{
"providers": {
"example": {
"accountPool": {
"strategy": "quota",
"sessionAffinity": "conversation", // pin a turn chain to one account
"rotateOn": ["429", "401", "403:quota"] // classes that rotate instead of failing
}
}
}
}# retrying this must settle the SAME ledger operation, not open a second one
ocx codex reset-credit --account me@example.com --operation-id 6f1c1f2e-0f2a-4a1e-9a1b-0d2f7c8e5b31Alternatives or workarounds
Operators currently work around this by running one account per proxy instance and load-balancing externally, which defeats quota-aware selection and makes pool health invisible.
Additional context
Landed prior work referenced above: 816f3a1 (generic account failover), 7c68768 (reset-credit operation ledger).
Absorbs #695, #1062, #1977, #2275. Credit for the original analysis belongs to @luwei1990, @agentHits, @dbc-hbin, and @luvs01.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
coderabbitai commentedon Sep 3, 2026
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
lidge-jun commentedon Sep 3, 2026
리뷰 · 우선순위 72 / 80
이 이슈는 OAuth 계정 풀을 “고르고, 그 계정에 대화를 붙이고, 계정 하나가 죽어도 풀이 스스로 넘어가게” 만들려는 남은 작업을 한곳에 모은 것입니다. 지금
devHEAD(664d80c76)에는 1차 구현이 이미 있습니다.src/oauth/generic-account-failover.ts는 파일 머리부터 “Generic OAuth multi-account 429 failover”이고, 429 쿨다운·로스터 순회·쿼터로 첫 계정을 고르는 일만 합니다. 주석이 직접 “Anthropic 풀보다 좁다. 세션 고정 없음”이라고 말합니다. 그래서 긴 대화가 중간에 다른 계정으로 새는 구멍이 그대로입니다. 401이나 제공자가 분류한 403은 풀 전체 실패로 끝나고, 옆에 멀쩡한 계정이 있어도 안 넘어갑니다.본문이 가리키는 경로도 현재
dev와 맞습니다. 계정별 쿼터는src/providers/quota.ts와gui/src/components/provider-workspace/ProviderAuthPanel.tsx에 보입니다. 그런데 풀 전체 용량 화면과 “이 요청이 어느 계정 탓인지” 표시는 없습니다. 수동 reset-credit 쪽은 본문이 인용한 원장 커밋이 이미 들어 있다고 하고, 남은 것은 재시도가 같은 operation id로 붙는 내구성입니다. 흡수된 #695, #1062, #1977, #2275는 모두 닫혀 있고, 이 이슈가 그 남은 범위를 이어 받습니다. types/config 분할 캠페인에 치여 닫을 항목이 아닙니다. 오히려 나중에accountPool.sessionAffinity/rotateOn을 설정에 넣으면src/types/config.ts를 만지게 되므로, 그 패치는 분할 이후에 맞추면 됩니다.왜 지금 우선순위가 높은가. 운영자가 지금 쓰는 우회는 “프록시 하나 = 계정 하나”입니다. 그러면 쿼터로 고르는 이득이 사라지고, 풀 건강도 안 보입니다. 이미 있는 429 회전을 401/403과 대화 고정까지 넓히는 일은 새 제공자를 또 넣는 것보다, 있는 풀을 실제로 쓸 수 있게 만드는 일에 가깝습니다. 본문 예시의
rotateOn: ["429", "401", "403:quota"]처럼 클래스별로 나눈 것은 중요합니다. 모든 403을 회전하면 권한 없는 계정이 무한히 다음 계정으로 넘어갈 수 있습니다. “쿼터/구독 게이트로 분류된 403만”이 안전한 기본값입니다.세션 고정은 Anthropic 경로(
src/oauth/anthropic-routing.ts)가 이미 가지고 있으니, generic 풀이 그걸 복사할지, 공통 헬퍼로 올릴지가 설계 분기입니다. 파일 주석이 이미 Anthropic이 affinity를 가진다고 가리키므로, 중복 구현보다 그 규칙을 generic으로 여는 쪽이 덜 흔들립니다. 풀 건강 UX는 GUI 작업이라 회전 로직과 따로 열 수 있습니다. reset-credit operation id는 CLI 계약이라 세 번째 조각입니다. 한 PR에 다 넣지 않는 편이 맞습니다.src/oauth/generic-account-failover.ts- 429만 회전하고 세션 고정이 없습니다. 주석이 그 한계를 이미 적고 있습니다.src/providers/quota.ts,gui/src/components/provider-workspace/ProviderAuthPanel.tsx- 계정별 쿼터는 보이지만 풀 합산·계정 귀속은 없습니다.본문 예시
rotateOn- 401/403을 전부 회전할지, 쿼터로 분류된 403만 회전할지 기본값을 정해야 합니다.흡수된 #695/#1062/#1977/#2275 - 닫혀 있고 이 이슈가 남은 범위입니다. 다시 열 필요 없습니다.
메인테이너의 판단이 필요한 지점
403:quota만 회전할지, 제공자 분류 403 전체를 회전할지.너의 추천
이 이슈를 열어 두고, 첫 PR은 generic failover에 대화 고정 + 401 및 쿼터-403 회전만 넣으세요. 풀 건강 화면과 reset-credit 내구성은 후속 PR로 나누는 것이 현재
dev와 맞습니다. 흡수된 원본 이슈는 다시 열지 마세요.이 댓글은 grok-bot이 작성했습니다
nordz0r commentedon Sep 5, 2026
Live v2.42.0 follow-up for the scope consolidated from #695.
Environment (redacted): Debian 12 / Kubernetes container, provider
google-antigravity, five stored OAuth accounts. The provider config contains:{ "oauthAccountFailover": { "enabled": true, "strategy": "quota", "autoSwitchThreshold": 25 } }The installed
src/oauth/pool-settings-capability.tsDTO reportsinert: trueforstrategyandautoSwitchThreshold, andsrc/types/provider.tsstates that the selector does not consume them. The effective generic path can use cached per-account quota headroom for its existing initial preference and can rotate after a 429, but neither the configuredquotastrategy nor the 25% threshold controls that selector.This is not a claim that reactive 429 failover is broken; I did not manufacture quota exhaustion. It confirms that proactive generic OAuth selection cannot currently be relied on to obey the persisted strategy/threshold.
There is also a CLI visibility gap:
ocx account auto-switch <generic-oauth-provider> statusderivesenabledonly fromautoSwitchThreshold > 0and printsauto-switch: on(or"enabled": truein JSON), while dropping the API'sinertmarker. That can make a declared-but-unused threshold look active.The closing comment on #695 says to report any scope lost during consolidation here. Could #3375 explicitly retain selector consumption of
strategyandautoSwitchThresholdas an observable outcome? Until that lands, the CLI/API should refuse those writes or clearly label the values as declared/experimental and not active.A separate HTTP 400
User location is not supported for the API useaffected every tested account and is tracked in #3467. It is not a quota/429 signal, and this comment does not request rotating accounts on that error.No account identifiers, emails, project IDs, tokens, IP addresses, or private infrastructure details are included.
Ingwannu commentedon Sep 9, 2026
Retaining the selector-consumption requirement here: current dev still explicitly marks generic OAuth strategy and autoSwitchThreshold as declared/inert. A stored 25% value must not be treated as proof that the selector enforces a 25% switch.
The CLI visibility part has a separate landed fix: #3797 merged on September 6, 2026. I rechecked current dev src/cli/account-extended.ts: generic JSON output keeps enabled:false, exposes poolEnabled separately, and carries inert; text says inactive/not applied, or unavailable when capability is unknown. tests/cli/cli-account-pool-verbs.test.ts covers status, writes, unset versus zero, and malformed/unknown capability responses. That source-level result does not tell us which code your v2.42.0 process is running, so it does not invalidate your observation.
Keeping #3375 open for actual selector consumption and the remaining lifecycle scope. Reactive 429 handling remains distinct; the separate region/policy 400 is not a signal to rotate through accounts. No duplicate CLI PR or live account-rotation probe is needed.
agentHits commentedon Sep 16, 2026
Hi @lidge-jun and @Ingwannu 👋
I wanted to share a quick update on my end regarding the progress towards completing the OAuth account pool lifecycle (#3375), as I have been actively working on this:
dev.devhead183119329, ported missing regression tests from feat(oauth): model-family aware quota headroom ranking and failover #4299 with attribution, classified GPT-OSS to the Claude family, and fixed all fixtures). All 22/22 unit tests pass. It is ready for themaintainer-sponsoredlabel.waitForCooldownMs, eliminating session stalls in Codex.Next Step: Full Account Pool Rotation (Part 2)
I have already fully developed and tested the complete, seamless multi-account pool rotation system in my local environment today. It includes:
reset-firstrotation strategy (prioritizing accounts whose 7-day weekly allowance resets earliest, ensuring expiring quotas are consumed first).Everything is running smoothly in my daily workflow without stalls or interruptions. As soon as you review and merge the foundational PRs (#4560, #4676, and #4824), I am ready to submit this final piece as a clean PR to share it with the whole OpenCodeX community! 🚀
agentHits commentedon Sep 16, 2026
Here are live screenshots from my working local dashboard (
http://localhost:10100/#providers) with Google Antigravity accounts and real-time quota telemetry across three languages (with all logins and credentials masked for privacy):1. English (EN)
2. 한국어 (KO - Korean)
3. Русский (RU - Russian)
The workspace shows:
reset-firststrategy selector directly in the toolbar.devin-ai-integration commentedon Sep 24, 2026
Triage:
priority: P2— OAuth account-pool lifecycle umbrella (several PRs build on it).Criteria (P2): Medium: provider/client-specific bug with a workaround, bounded enhancement tied to a tracked issue, perf, or CI reliability.
Matched pull requests:
priority: P3]priority: P2]priority: P3]priority: P1]priority: P1]lidge-jun commentedon Sep 27, 2026
Release train 4 progress on this umbrella:
555ef68ac6) adds operator pause/resume for generic OAuth pools.ae6b0ba913) adds Antigravity-only pre-output 401 recovery onto a sibling account.Still open: session affinity for generic pools, selectors that use the declared strategy and threshold, pool health and attribution, scheduled warmup, and 403 rotation (see #5099). Stable reset-credit identity was already on
devbefore this train.