Skip to content

[Feature]: Allow Anthropic account-pool quota routing to select by weekly usage #2539

Description

@Yoonkeee

Area

Authentication and account pool

What are you trying to accomplish?

I run the Claude OAuth account pool (anthropicAccountPool.enabled: true) with several Claude subscriptions, and I want new sessions to be spread evenly across those accounts over a rolling 7-day window rather than only over the current 5-hour window.

Anthropic enforces two limits per account at the same time: a 5-hour rolling window and a weekly (7-day) rolling window. On a heavy coding day the 5-hour numbers of two accounts can look nearly identical while their weekly budgets are far apart — one account may be at 20% weekly and the other at 85% weekly. Today the pool treats those two accounts as interchangeable, so it keeps feeding the account that is close to burning its weekly allowance. By Thursday that account is weekly-limited for the rest of the week while the other one still has most of its weekly budget unused.

The outcome I need is: let the operator choose which quota window the pool optimizes for, so a multi-day workload finishes the week with the accounts drained roughly evenly instead of one account exhausted and the rest idle.

What prevents this today?

The pool's quota routing strategy scores accounts only by the 5-hour window. On current dev (98ed186c7), src/oauth/anthropic-routing.ts has:

function hasKnownUsage(accountId: string): boolean {
  const quota = getCachedProviderAccountQuota(PROVIDER, accountId);
  return typeof quota?.fiveHourPercent === "number" && Number.isFinite(quota.fiveHourPercent);
}

function usageScore(accountId: string): number {
  const quota = getCachedProviderAccountQuota(PROVIDER, accountId);
  if (!quota || typeof quota.fiveHourPercent !== "number" || !Number.isFinite(quota.fiveHourPercent)) {
    return UNKNOWN_USAGE_SCORE;
  }
  return Math.max(0, Math.min(100, quota.fiveHourPercent));
}

fiveHourPercent is the only usage field the whole module reads — it appears at lines 147, 152, and 155, and weeklyPercent appears nowhere in the file. Every usage-aware decision therefore inherits the 5-hour-only view: the lowest-usage pick for a new session, the fill-first "is the active account still under threshold" check, and the autoSwitchThreshold comparison.

The weekly number is already collected and cachedsrc/providers/quota.ts carries weeklyPercent on the per-account quota record (declared at line 94, populated at lines 518/664/713/749, and surfaced in the dashboard and ocx account output). So the data the router would need is present and fresh; it is simply never consulted when choosing an account.

There is no configuration escape hatch either. AnthropicAccountPoolConfig (src/oauth/anthropic-routing.ts:45-53) exposes only enabled, autoSwitchThreshold, strategy, and stickyLimit — nothing selects the window. The only workarounds available to an operator today are to manually disable an account before it burns its weekly budget, or to set autoSwitchThreshold very low, which changes 5-hour churn behaviour without ever looking at the weekly number.

What should OpenCodex do?

Add an opt-in quotaWindow setting to anthropicAccountPool that selects which quota window usage-aware selection scores against:

  • "five-hour" (default) — exactly today's behaviour. When quotaWindow is absent, unset, or unrecognized, account selection must be byte-for-byte identical to current dev. Existing installs must observe no change whatsoever.
  • "weekly" (new, opt-in) — rank candidates by their weekly usage percentage so a multi-day workload drains accounts evenly across the 7-day window.
  • "max-utilization" (new, opt-in) — rank by the higher of the two known percentages, i.e. by whichever window is closer to its limit, for operators who want the most conservative pick.

Behaviour that must be preserved and constrained:

  1. Session affinity is unchanged. The window only affects which account a new session binds to. A live session keeps its account exactly as it does today; there is no mid-session rebalancing.
  2. 429 cooldown and failover are unchanged. Cooldown, quarantine, reauth handling, and the per-request failover cap keep their current semantics. Selection must never return null in a case where it returns an account today.
  3. Weekly mode must not pick a 5-hour-exhausted account. An account whose 5-hour window is already at 100% is unusable right now no matter how good its weekly number looks. In weekly mode such accounts must be skipped — unless skipping them would leave no candidate at all, in which case the existing behaviour wins and an account is still returned.
  4. Unknown usage never wins. An account with no collected/parsed value for the configured window must sort last, never first, exactly as unknown 5-hour usage does today (UNKNOWN_USAGE_SCORE). A never-probed account must not be mistaken for an idle one.
  5. Deterministic ties. When two accounts score equal in the configured window, break the tie by the lower 5-hour percentage; if that is still equal, keep the existing stable ordering so selection stays reproducible.
  6. Scope. The setting belongs to the Anthropic pool only. It applies to the strategies that actually consult usage (quota and fill-first) and is inert under round-robin, which ignores usage by design. It should be reachable from the dashboard's account-pool settings and the management API alongside strategy and autoSwitchThreshold, and documented in the provider/Claude Code docs.

Example usage or interface

Configuration (opt-in; omitting quotaWindow keeps today's behaviour):

{
  "anthropicAccountPool": {
    "enabled": true,
    "strategy": "quota",
    "quotaWindow": "weekly"
  }
}

Concrete before/after with two pooled Claude accounts:

account 5-hour usage weekly usage
A 30% 85%
B 35% 20%

Before (today, and still the default): a new session picks account A, because 30% < 35% in the 5-hour window — even though A has only 15% of its weekly budget left and B has 80% left.

After, with "quotaWindow": "weekly": the same new session picks account B, because 20% < 85% in the weekly window, leaving A's remaining weekly budget for later in the week. If A's 5-hour window were instead at 100%, weekly mode would skip A regardless of its weekly figure; if B had never been probed and its weekly value were unknown, B would sort last rather than first.

Alternatives or workarounds

  • Manual account toggling / a very low autoSwitchThreshold. This is what I do today. It only manipulates the 5-hour window and requires me to watch the dashboard and disable an account by hand before it burns its weekly allowance; it is exactly the toil this proposal removes.
  • Always scoring by the weekly window (no config option). Rejected: it would silently change selection for every existing pool user, and the current 5-hour behaviour was a deliberate choice in the pool implementation. Opt-in with a "five-hour" default keeps existing installs untouched.
  • Always scoring by "whichever window is closer to its limit". Useful, but as a forced default it would surprise operators who intentionally want burst behaviour inside the 5-hour window. Offering it as the explicit "max-utilization" value gives that behaviour to operators who ask for it without imposing it.
  • A reset-time-aware "reset-pressure" score (weighting usage by how soon each window resets, so an account 5 minutes from its reset is not treated the same as one 6 days out) was considered and deliberately deferred to a separate follow-up proposal. It needs reset-timestamp trust rules and clock-skew handling of its own, and folding it in here would make this change much harder to review. This proposal stays scoped to selecting an existing, already-collected window.

Additional context

Verified related issues and pull requests on this repository (each checked with gh before filing):

Code references are against dev at 98ed186c7: src/oauth/anthropic-routing.ts (hasKnownUsage / usageScore at lines 145-156, pickLowestUsage at 194-208, AnthropicAccountPoolConfig at 45-53) and src/providers/quota.ts (weeklyPercent, line 94 and its population sites).

I am happy to implement this and open a pull request against dev if the direction is acceptable.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions