[Bug] Anthropic pool: Retry-After-sourced cooldowns do not recover after a fresh usage read proves headroom (plan upgrade / window reset)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 54/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- authentication, backend-api-design
Research direction
Start with the cooldown creation and recovery paths in src/oauth/anthropic-routing.ts, then read src/providers/quota/anthropic-cooldown-recovery.ts and the related issues #3973, #915, #433, and #6692. Trace how shared-quota refusals record rejected windows and how a fresh usage probe settles recovery. Done means a fresh, complete read showing headroom clears the cooldown for every rejected window, including Retry-After-sourced cooldowns.
Written by the indexing model from the issue text.
Description
Client or integration
Codex CLI (routing through the local proxy; the same proxy path serves Codex App sessions)
Area
Authentication and account pool
Summary
With the Anthropic OAuth account pool enabled, all Claude requests were refused locally with 429 (failureStage=headers-only, 7-9 ms, no upstream attempt) and service.log showed [anthropic-pool] no eligible replacement; returning 429.
All five unpaused accounts were in cooldowns whose deadlines equaled each window reset time. One of them then had its usage counters reset by a plan-tier upgrade: fresh usage observations went from 100/66 percent to 0/0 percent on both windows (verified with ocx account list anthropic --quota --refresh, updatedAt after the reset).
The account stayed out of the pool anyway, because its active cooldown had cooldownSource retry-after and the recovery contract only reconciles reset-derived cooldowns. The pool kept refusing every Claude request until the operator ran ocx account clear-cooldown anthropic <id>.
Expected: once a fresh, complete usage read proves headroom in every window that the refusal reported as rejected, reconcile the cooldown regardless of which deadline source was recorded at refusal time (Retry-After vs reset-derived). A refusal classified shared-quota carries the same evidence in both cases.
Reproduction
- Enable the Anthropic account pool (round-robin, weekly window). Drive accounts to their window limits so 429s are recorded with
anthropic-ratelimit-unified-*-status: rejectedplus aRetry-Afterheader. The cooldown record then uses cooldownSourceretry-afterwith a deadline equal to the window reset. - Before that deadline, make the account usage drop below the limit on every previously rejected window (plan upgrade, a reset grant, or the window rollover).
- Observe:
ocx account list anthropic --json --quotareports fresh fiveHourPercent 0 / weeklyPercent 0, but health staysRate limited, and requests keep failing locally with 429. Onlyocx account clear-cooldown anthropic <id>restores service; it takes effect immediately with no restart.
Step 2 could not be repeated deterministically because the upgrade happened on a live account; the sequence above is what the recorded timestamps and quota observations show.
Version
2.83.0 (@bitkyc08/opencodex, npm global install, bundled Bun runtime)
Operating system
macOS (Apple Silicon), local standalone proxy, port 10100
Provider and model
anthropic / claude-opus-5-5 (the whole Claude family shares this pool)
Logs or error output
[anthropic-pool] 429 on pXXXXXX; failing over to pYYYYYY
[anthropic-pool] no eligible replacement; returning 429
Usage ledger rows for the refusals: status 429, errorCode rate_limit_exceeded, failureStage headers-only, durationMs 7-9.
Account health observed for the affected account: Rate limited, then Rate limited again after a fresh read showed 0/0 usage, then Healthy only after clear-cooldown.
Redacted configuration
{
"anthropicAccountPool": {
"enabled": true,
"autoSwitchThreshold": 99,
"strategy": "round-robin",
"stickyLimit": 1,
"quotaWindow": "weekly"
}
}
Source-level analysis (v2.83.0)
src/oauth/anthropic-routing.ts(around line 1092): the cooldown record prefersretry-afterover the reset-derived deadline, andrejectedQuotaWindowsis only attached on the reset-derived branch.src/oauth/anthropic-routing.ts(around line 370):captureAnthropicCooldownRecoveryreturns null unlesscooldownSource === "reset-derived", soprobeAnthropicQuotaWithRecovery/settleAnthropicCooldownRecovery(src/providers/quota/anthropic-cooldown-recovery.ts) can never clear a Retry-After-sourced cooldown, even when the refusal was classifiedshared-quotaand every rejected window now reads below 100 percent.- Suggestion: for
shared-quotarefusals, attachrejectedQuotaWindowsin both branches, and let the existing fresh-probe recovery claim Retry-After cooldowns too (or re-derive the deadline from a fresh read).
Related: #3973 (manual reset credit; here no credit was consumed and the counters were read fresh via the usage probe), #915, #433, #6692.
- Dominant language
- TypeScript
- Stars
- 16.9k
- Forks
- 1.3k
- Avg merge
- 6h 54m
- Merged PRs (30d)
- 606
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from lidge-jun/opencodex
-
[Provider compatibility] volcengine-coding-plan: response.incomplete relays non-canonical incomplete_details.reason ("length")Possibly taken A pull request linked to this issue is open or already merged. Openprovider provider-compatibility streaming
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
bug catalog
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
lidge-jun/opencodex#7075 · 2 comments ·
Maintainers usually reply within 1 day
-
account-pool enhancement needs-design proxy
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
lidge-jun/opencodex#6737 · 1 comment ·
Maintainers usually reply within 1 day
-
[Bug]: Codex still retries an Anthropic refusal five times because response.incomplete ignores retryable:false (follow-up to #4312)Possibly taken A pull request linked to this issue is open or already merged. Openbug streaming
Difficulty 3/5 1-2 days Newbie friendliness 45/100
Maintainers usually reply within 1 day
-
provider provider-compatibility tools
Difficulty 4/5 3-5 days Newbie friendliness 22/100
lidge-jun/opencodex#7118 · 1 comment ·
Maintainers usually reply within 1 day
All issues in lidge-jun/opencodex
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
apache/rocketmq-dashboard#6872 ·
Maintainers usually reply within 3 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
fab-geocommuns/RNB-site#539 ·
Maintainers usually reply within 2 days
-
Guest multiview tile label leaks raw virtual-source-id "Whip" instead of the guest name / slot labelOpen
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Eyevinn/open-live-studio#211 ·
Maintainers usually reply within 1 day
-
documentation good first issue
Difficulty 2/5 Half a day Newbie friendliness 72/100
sujithputta02/Esapay#13 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
ehmpathy/rhachet-roles-bhuild#468 ·
Maintainers usually reply within 1 day