Fix: revalidate throttled accounts so a stale 429 hold cannot require a restart#77
Merged
MagicalTux merged 2 commits intoJul 6, 2026
Conversation
A 429 with a long retry-after puts an account into an in-memory hold, and failover means one client request during a 429 burst can throttle every account at once. While a hold lasts nothing revalidates it, so when the burst was transient (e.g. retries piling up after a network drop and reconnect) the proxy keeps refusing with synthetic 429s for up to an hour even though upstream is healthy again. The hold is not persisted, which is why restarting teamclaude "fixed" it. Reuse the existing quota-probe machinery: after a floor (TEAMCLAUDE_THROTTLE_PROBE_FLOOR_MS, default 60s), a throttled account becomes probe-eligible when nothing else is available, one probe per probe interval. The probe is a real client request; any non-429 response clears the hold and returns the account to rotation, a 429 re-arms it with a fresh retry-after and pushes the next probe out a full floor. Reproduced and verified in a Docker sandbox: healthy -> 429 burst (both accounts held 3600s) -> upstream healthy again. Before: refuses until restart. After: self-recovers on the first request past the floor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 6, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
After an internet drop and reconnect, teamclaude sometimes keeps failing every request even though the network and the API are fine again. All accounts show
throttled, Claude reports API errors, and the only thing that helps is restarting teamclaude.This replaces #73, which blamed dead pooled connections. Sandbox testing (below) disproved that diagnosis and found the real one.
What testing showed
All of this was measured in a Docker sandbox: a mock Anthropic upstream, teamclaude in a container, and a true silent network blackhole (iptables dropping all packets, no RST or FIN, exactly what a wifi drop looks like).
The actual bug
A 429 response arms an in-memory hold on the account (
rateLimitedUntil, up to 1 hour fromretry-after). Two properties turn that into a lockout:The hold is not persisted. That is the whole reason restarting "fixes" it: the restart wipes the hold, nothing else.
sequenceDiagram participant C as Client participant T as teamclaude participant A as API Note over C,A: network drops, then reconnects C->>T: request (retry burst) T->>A: try account alpha A-->>T: 429, retry-after 3600 T->>A: failover to account beta A-->>T: 429, retry-after 3600 Note over T: both accounts held in memory for 1h T-->>C: 429 "all accounts exhausted" Note over A: API fully healthy again C->>T: request T-->>C: 429 (hold still active, nothing rechecks it) Note over C,T: dead until hold expires or teamclaude restartsReproduced end to end in the sandbox: healthy → 200, then 429 burst → both accounts held 3600s, then upstream healthy again → still refused every request, then restart → instant 200.
Fix
Reuse the existing quota-probe machinery (the same mechanism that revalidates stale soft-quota reads). After a floor (
TEAMCLAUDE_THROTTLE_PROBE_FLOOR_MS, default 60s), a throttled account becomes probe-eligible when no account is available, limited to one probe per probe interval. The probe is a real client request, so it costs nothing extra:retry-afterand pushes the next probe out a full floorA genuine long rate limit is still respected: the fleet sends at most one request per minute against it, and every re-arm is based on what upstream just said rather than a stale snapshot.
Same sandbox, with the fix:
Recovery without restart, on the first request past the floor. Unit tests cover the floor, the probe gating, clearing on non-429, re-arming on 429, and hold expiry.