Repository navigation
Description
Summary
When the account a conversation is pinned to returns HTTP 408, 500, 502, 503, 504 or 529, or the connection fails, the Account Pooler retries the request on the next account at once. If that account answers, the pool rebinds the session pin to it permanently. The conversation never returns to its first account, even after that account recovers a second later. Prompt caches belong to one account, so the move rewrites the whole cached context on the new account at the cache-write price, for a failure that was not about the account. I expected the pool to retry the pinned account briefly first and, if it still fails over, to keep the pin, as it already does for a model-family limit (familyDetour).
Versions and environment
- bb 0.48.1 (
bb --version), installed from npm (bb-app), andmainatdbb247c60f46494579adb40b80efa7c8a861b35c. - Linux 7.0.0 (Azure), Node v22.23.4.
- Provider: Claude Code through the Account Pooler, two or more Claude accounts in the pool.
Steps to reproduce
The failure is transient upstream, so the faithful repro is a test at the exact code path. With two API-key accounts in plugins/account-pool/src/server.test.ts (affinityFixture):
- Send a Claude request with session
warm. It is served bysk-first, andwarmis pinned tosk-first. - Make
sk-firstanswer the next request with HTTP 503 (or throw fromfetchto simulateETIMEDOUT). - Send
warmagain. It is served bysk-second. - Let
sk-firstrecover. Sendwarmagain.
The existing test keeps %s sessions and the pool cursor on the third account after two failures asserts this behaviour on main: after one outage, warm stays on the fallback account.
Expected vs actual
Actual (upstream attempts, main): ["sk-first", "sk-first", "sk-second", "sk-second"]. Step 4 goes to sk-second; the pin moved in step 3 and never returns.
Expected: step 3 retries sk-first with a short backoff before it fails over, and step 4 goes back to sk-first: ["sk-first", "sk-first", "sk-first", "sk-first", "sk-second", "sk-first"] when the outage outlasts the retries.
Evidence
- Failover after a transient status or a connection error: hub.ts#L576-L597, hub.ts#L650-L736. Each breaks out to the next account with no same-account retry.
- The rebind: hub.ts#L999-L1004.
attempted.has(bound.account.id)rebinds whenever the pinned account was tried in this request, whatever the reason. OnlyfamilyDetouris exempt. - Observed on 0.48.1 pool logs:
ETIMEDOUTbursts of 3 to 5 requests. Each burst can move every active conversation off its account. The extra cache writes were not measured: the pool records no per-request token usage.
What I ruled out
- Not Account Pooler: 30-minute session pin is shorter than the 1-hour prompt cache #5262 (pin lifetime shorter than the 1-hour cache): this move happens within seconds of the last request.
- Not Keep Account Pooler accounts past the switch threshold usable until the provider refuses them #6849: that change keeps pins past the switch threshold. It is on
mainbut not in 0.48.1, where pinned sessions still leave at the threshold.mainno longer has that half of my original report, so this issue covers only transient errors. - 401, 403 and quota refusals should still move the pin: the account cannot serve, so that part is correct.
Suggested priority and effort
Hits every multi-account Claude pool during upstream overloads or network blips. No data loss, but each move costs a full cache rewrite on the fallback account. Small, plugin-local change.
AGENT GENERATED
Activity
bb-slop-cop commentedon Nov 4, 2026
🚨 SLOP COP 🚨 ·
new-issue-autopilotproviders,provider-claude-code,confirmed-repro.8d131a26); existing routing controls pass.Retry a pinned Account Pooler account before failing over on transien…