fix(anthropic-subscription): block a spent account until its usage-limit reset (#3462)
Fixes #3462
What
anthropic-subscription: an account-level usage limit now blocks the account until the reset its message names. When the message names no reset, the block lasts until the reset that the account's fully used limit window last reported (rate_limit_event). Only when neither is known does it keep the 60 s default. Model-family limits and plain 429 rate limits are unchanged.
Why
Claude Code's usage-limit prose carries no Retry-After, so blockedAccount (failover.ts:121 on main) blocked every spent account for 60 s. A pinned account outranks the account the session moved to (affinity.ts:104-108). So every 60 s the session retried the spent account, failed, and fork-resumed the healthy one again. omo#10304 measured that fork at 0.8-0.9M cache-write tokens.
Evidence
- RED on main (c335bd5726):
test/suite/regressions/3462-anthropic-subscription-usage-limit-reset-block.test.tsfailed 2 of 4. Over 20 turns 30 s apart, 9 turns after the first retried the spent account (expected 0), and the window case stored a 60000 ms block (expected 7200000). - GREEN: 4 of 4 pass. Related suites pass 38 of 38:
anthropic-subscription-failover, 2891 stickiness, 1432 failover reattach, and the affinity/account suites. bun run checkpassed in the pre-commit hook.
Credit: the mechanism was reported by a community member in the OmO community chat.
Summary by cubic
Fixes anthropic-subscription failover so an account that hits its usage limit stays blocked until the limit resets, instead of being retried every 60 seconds. Each retry used to hit the spent account again, re-forking the session on a healthy account and re-writing its prompt cache.
- The block lasts until the reset named in the limit message ("resets in 1 hour"), or, when none is named, until the reset of the account's fully used limit window as the SDK last reported it — capped at the limit's own window.
- Only Claude Code's account usage-limit errors take the long block; limits with no known reset and plain 429 rate limits keep the 60-second default.
- A working account that serves a turn clears its stored block best-effort — a failure to clear is logged, never fails or replays the finished turn, and a success clears only a block stored before its attempt started, so a newer block another session wrote meanwhile stays.
/claude-account unblock <id>lifts a usage or rate-limit block by hand; auth blocks keep their re-probe. - Adds regression tests covering reset-named, reset-less, window-fallback, cap, guards, plain-rate-limit, and newer-block cases.