fix(anthropic-subscription): keep the failure that blocked an account and log every block
Summary
When one Anthropic Subscription account fails while every other account in the pool is already blocked, the turn now reports that account's own failure (with the pool refusal attached as context) instead of a bare "All Claude accounts ... are currently blocked". Every account block that is saved is logged as account_blocked, from the failover and from the background usage poll, and the blocked-pool error/guidance lists each account's state and names the account that needs /login.
Root cause
packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/failover.ts(runFailovercatch block): after saving the block,event.nextAccount = options.selectFn(accounts)ran insidetry/finally. With every other account blocked,selectAccount(affinity.ts) throwsAllAccountsBlockedError, which escaped and replaced the original failure (a refresh failure or a 401).- The block was only emitted as an in-memory event; nothing was logged. The usage poll's
markAuthRejected(subscription-limits/usage-accounts.ts) could also add anauth_errorblock silently.
Fix
failover.ts: catchAllAccountsBlockedErrorfromselectFnon the failover-event path and throw the originalClassifiedSdkErrorwith the refusal attached aspoolState; any other error still propagates. Logaccount_blocked {account, reason, until, shared, detail}whenever a block is saved.account-block-log.ts(new):logAccountBlocked, through the extension's existing session.log boundary (logContinuityEvent); detail capped at 200 chars.usage-accounts.ts:markAuthRejectedlogsaccount_blocked(detail: usage_poll_token_rejected) when it actually locks the slot.affinity.ts:AllAccountsBlockedError.accounts(name, reason, until); the message keeps its first sentence unchanged and appends each account's state, naming/login anthropic-subscriptionfor auth-blocked accounts.guidance.ts/stream-guidance.tsshow the same lines to the user.
RED -> GREEN
RED (test/suite/regressions/4671-pool-block-keeps-cause.test.ts on origin/main + the test only): 3 failed
FAIL ... keeps the original auth failure when no other account can be selected, and logs the block
AssertionError: expected AllAccountsBlockedError: All Anthropic Su… { …(4) } to be an instance of ClassifiedSdkError
FAIL ... lists every account's state and names the account that needs /login
AssertionError: expected undefined to deeply equal [ { name: 'alpha', …(2) }, …(1) ]
FAIL ... logs the block the usage poll saves when the token endpoint rejects a login
AssertionError: expected [] to deeply equal [ { event: 'account_blocked', …(1) } ]
GREEN: the regression plus failover, affinity, guidance, model-scoped-limit, usage-accounts, credential-error-taxonomy and stream tests: Test Files 8 passed (8) / Tests 147 passed (147). Gates (each its own process via the gated push): focused vitest, root tsc --noEmit, check-pr-changelog, lint-ratchet, oxfmt --check, git diff --check.
Fixes #4671
Part of code-yeongyu/oh-my-openagent#11683
Summary by cubic
Fixes #4671: when one Anthropic Subscription account fails while every other account is already blocked, the turn now reports that account's own failure instead of hiding it behind "all accounts blocked". Every block that is saved is logged as account_blocked with the account named by seat digest only, and the pool refusal is attached as context rather than replacing the failure.
Changes
runFailoverthrows the original failure with theAllAccountsBlockedErrorrefusal attached aspoolStateand as the error'scause; other selection errors still propagate.- The turn error keeps the soonest automatic retry reachable, so goal mode's
providerRetryAtstill parses it. - Every saved block — from failover and from the usage poll's
markAuthRejected— writes anaccount_blockedlog line with reason, until, shared, and detail (capped at 200 characters). AllAccountsBlockedErrorcarries each candidate's state and appends it to the message; guidance lists the same per-account lines and names the account needing/login.- The session.log data-key allowlist admits
seat,until, andsharedso those fields survive in the real log.