Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[Bug] Anthropic pool: Retry-After-sourced cooldowns do not recover after a fresh usage read proves headroom (plan upgrade / window reset)

Closed
#6,989 0 comments 0 reactions 0 assignees View on GitHub

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

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

account-pool bug
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
  1. Enable the Anthropic account pool (round-robin, weekly window). Drive accounts to their window limits so 429s are recorded with anthropic-ratelimit-unified-*-status: rejected plus a Retry-After header. The cooldown record then uses cooldownSource retry-after with a deadline equal to the window reset.
  2. 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).
  3. Observe: ocx account list anthropic --json --quota reports fresh fiveHourPercent 0 / weeklyPercent 0, but health stays Rate limited, and requests keep failing locally with 429. Only ocx 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 prefers retry-after over the reset-derived deadline, and rejectedQuotaWindows is only attached on the reset-derived branch.
  • src/oauth/anthropic-routing.ts (around line 370): captureAnthropicCooldownRecovery returns null unless cooldownSource === "reset-derived", so probeAnthropicQuotaWithRecovery / settleAnthropicCooldownRecovery (src/providers/quota/anthropic-cooldown-recovery.ts) can never clear a Retry-After-sourced cooldown, even when the refusal was classified shared-quota and every rejected window now reads below 100 percent.
  • Suggestion: for shared-quota refusals, attach rejectedQuotaWindows in 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from lidge-jun/opencodex

All issues in lidge-jun/opencodex

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.