Repository navigation
Conversation
A token-endpoint 400/401 on a non-forced refresh no longer errors an account whose access token is still valid. The hub stores a SHA-256 fingerprint of the rejected refresh token in plugin KV, never sends it again from requests or the background loop (also across restarts), and sets the account error only when the access token expires or upstream rejects it. Before recording a rejection it rereads the stored secret and uses a changed refresh token instead. While an account is errored because its refresh token was rejected, each background tick rereads the stored secret and recovers the account when the refresh token has changed. Setting or clearing an account error now publishes ACCOUNT_POOL_ACCOUNTS_CHANGED. Fixes get-bb#6167 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymichael
added a commit
that referenced
this pull request
Oct 30, 2026
## Human comments ## What was wrong Anthropic and OpenAI refresh tokens are single use and rotate, so two programs holding one login sign each other out at the next refresh. bb created that situation twice. `bb pool account add --import` copied the machine's Claude Code or Codex login, refresh token included, into the pool (and the docs recommended it). Pool-routed Claude Code threads also kept refreshing the machine's own Claude login even though they never use it. The result was pool accounts failing with `invalid_grant` (#6167), the machine's login dying after the pool was turned off (#4352), and routed threads touching host logins (#6171). There was also no way to fix an account's login in place (#6170). ## What changed - **Import removed (Claude and Codex).** Removed `--import`, the `account.add` import source, the "Import from this machine" settings item, and the host credential readers (`~/.claude`, the Keychain, `~/.codex/auth.json`). Sign-in already works without a browser on the server (paste a code for Claude, a device code for Codex), so import could do nothing sign-in can't. A warned hand-over wouldn't be real, since bb can't sign the host's Claude Code or Codex out. `--login` now accepts `--label` and `--priority`, which used to work only with import. - **Re-login in place (Claude and Codex).** `bb pool account login <id>`, "Sign in again" in the account menu and dialog, and the RPC. `login.start` / `codexLogin.start` now take `{ kind: "add", label, priority }` or `{ kind: "relogin", accountId }`. Only the account's secret is replaced: - id, label, priority, enabled state, pinned conversations and quota observations are kept, and a disabled account stays disabled; - the error clears; - a login for a different account (Claude account UUID or email; ChatGPT workspace and email) is refused, and the stored login is kept; - the new secret is written only after any refresh already in progress for that account finishes (`hub.replaceSecret`). - **Routed Claude threads leave the host's Claude login alone.** The pool now adds `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1` to routed Claude Code; nested isolate mode sets it to empty. Checked on Claude Code 2.1.312: - without it, a routed session refreshed the stored login and wiped it on rejection; - with it, the stored login stays byte-identical and requests to the hub are unchanged (token, betas, model, 1-hour cache); - side effect: Claude Code strips `ANTHROPIC_AUTH_TOKEN` from tool subprocesses, so a nested `claude` needs `ANTHROPIC_AUTH_TOKEN="$BB_ACCOUNT_POOL_PARENT_TOKEN"` (documented). - Claude in Chrome (#4707), claude.ai connectors and Remote Control were already off behind the pool; the docs now point to `bb pool bypass`. - **Sign-in dialog:** errors now show inside the dialog with an inline "Start again" link, and Complete is disabled once the session is used up. Before, a failed Claude completion left the error hidden behind the modal. - **Docs:** account-pool skill and reference, PLUGIN_OVERVIEW (3949 of 4000 characters), CLI help, and the verify-bb feature map. - **Contracts:** `account.add` is now Claude API keys only (`{ provider: "claude", apiKey, label, priority }`). The `login.start` / `codexLogin.start` inputs changed as above. No server/daemon wire change (`HOST_DAEMON_PROTOCOL_VERSION` unchanged), no storage migration, no plugin SDK change. - **Not in this PR:** - Codex app-server auth isolation (follow-up on #6171). It's tested (hub-token command auth plus `cli_auth_credentials_store="ephemeral"`), but it costs routed threads their ChatGPT apps, and no Codex failure has been observed. - Adding accounts as disabled, and showing login expiry (#6170). - #6661 still fixes how the pool reacts to a rejected refresh (#6167). The two PRs conflict only in `server.test.ts`. ## How you verified - New `src/account-login.test.ts` runs the real hub against a fake OAuth server whose refresh tokens are single use and rotate. It covers: - an account sharing the machine's login losing the race, then re-login fixing it without spending the machine's token or refreshing the new one; - re-login keeping id, priority, disabled state, pins and quota; - refusal of a different Claude account (CLI) and of a different ChatGPT workspace (device flow). I mutation-checked these: removing the identity check or the error clear makes them fail. - About 30 `server.test.ts` cases used import only to set up OAuth accounts; they now seed existing accounts through the real `AccountStore`. Two UI tests cover the re-login target and the in-dialog error. - `pnpm exec turbo run test lint typecheck --filter=bb-plugin-account-pool --filter=bb-plugin-provider-codex`: 365 and 550 tests pass. `turn-failed-retry.test.ts` passes, and so does `generate:bb-official-marketplace`. - Live check on `pnpm start:worktree`, side by side with main, using the source CLI and a headless browser: - `--import` is rejected; - `--login --label --priority` works; - `account login <id>` works for Claude, Codex and disabled accounts, and is refused for API-key accounts and unknown ids; - a failed re-login leaves accounts unchanged; - the UI sends `{ kind: "relogin", accountId }`; - a routed Claude thread's `provider.env-resolved` shows `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1` from account-pool. - Not verified: completing a real re-login against claude.ai or ChatGPT (needs a human browser), and Windows. Refs #4352, #6167, #6170, #6171 🤖 Generated with [Claude Code](https://claude.com/claude-code) > AGENT GENERATED Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # plugins/account-pool/src/server.test.ts
get-bb#6691 added `bb pool account login <id>`, which replaces a login and clears the account error. Drop the secret-file reread and automatic recovery that this fix added for the same case, and clear the stored rejected-token fingerprint when an account signs in again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Human comments
What was wrong
The pool refreshes an OAuth token in the last 5 minutes before it expires. When the token endpoint rejected that refresh with HTTP 400 or 401, the pool marked the account as failed right away, even though its access token still worked. The background usage check did the opposite: it never marked the account as failed and resent the rejected refresh token every 5 minutes. The UI also wasn't notified when the status changed. #6691 removed the shared logins that usually caused the rejection, but a revoked login, or an account added with the old
--import, still runs into this. Report: https://get-bb.github.io/reports/issues/6167.htmlWhat changed
bb pool account login <id>) also clears the stored hash.ACCOUNT_POOL_ACCOUNTS_CHANGED.accounts-and-routing.md. No wire, CLI, RPC or settings changes.Decisions worth a look:
bb pool account login <id>does that and clears the error, so this PR leaves it out.bb pool account refreshstill retries the rejected token on purpose.enableclears the failure but keeps the stored hash.How you verified
src/refresh-rejection.test.tsruns the real hub, both adapters and real SQLite for Claude and Codex. 11 of its 13 tests fail on916d684and pass here; the other 2 check temporary refresh failures and pass on both.server.test.tsnow expects 200 and no resent token. It fails on base for both providers and passes here.Fixes #6167
🤖 Generated with Claude Code