#4707 · Pooled authentication and Chrome initialization
Verdict: PARTIALLY REPRODUCED · root-cause confidence: medium
1. TL;DR
The report describes Chrome browser tools disappearing when Claude Code uses the Account Pooler. Two clean source checkouts confirm the BB-side precondition: an enabled pool supplies ANTHROPIC_AUTH_TOKEN, and the Claude provider preserves it while adding --chrome. An isolated executable probe also verifies that the existing per-thread bypass removes the pool overrides, leaves the Chrome flag enabled, and does not change another thread. This Linux environment has no isolated macOS Chrome/native-login fixture, so the probe deliberately does not launch Claude Code or count Chrome MCP tools. The specific downstream native-login gate therefore remains unverified here; this is a partial reproduction, not confirmation of the reported zero-versus-22-tools result.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| Pool routing contributes a Claude auth override. | Verified | The real Account Pooler plugin emits a nonempty auth token through its provider-env contribution in both isolated probes. |
| Chrome is enabled, but the auth override reaches the provider unchanged. | Verified at the BB options boundary | The real provider options builder returns extraArgs: {chrome: null} and retains the pool auth/base URL. Static bridge and SDK-session code supports onward propagation. |
| Claude Code suppresses Chrome registration specifically because of the override. | Unverified | No real CLI, browser extension, or native claude.ai login was used. The reported CLI control outputs and internal auth gate were not independently rerun or inspected. |
| Per-thread bypass removes the pool env without disabling Chrome. | Verified for a pool with no parent | The real pool CLI handler changes routing; the same options builder then sees no pool token/base URL. A different thread remains pooled, and reverting the bypass restores the token. |
| Moving hub auth to a custom header safely fixes pooled inference. | Unverified | Hub code accepts a custom token header, but there was no custom-header inference test, native-auth identity check, remote-host exercise, or token-count request in this investigation. |
| The filing used the latest published desktop release. | Verified as of investigation | The trusted repository's latest-release API reports desktop-v0.44.0, published September 25, 2026 at 20:27:10 UTC. The reported macOS runtime itself was not accessed. |
3. Environment and scope
- Trusted repository: public
get-bb/bb, main commit7b13e936f10349263cf9f531a0adac7bd5f47642. - Two separate clean git checkouts; the second is a detached worktree at exactly the recorded base. Production files are unchanged.
- Linux x86_64, kernel
4.19.0-gvisor; Nodev22.19.0; pnpm9.15.0; frozen Claude Agent SDK dependency0.3.245. - The actual pool plugin uses the repository's SDK test host, real plugin SQLite storage/migrations, and fresh disposable files. The test host supplies only enrolled-host/provider-status metadata; no database is mocked.
- Each probe creates and deletes a new OS temporary data directory. Account material is synthetic; hub tokens are generated only for the fixture and never printed. No live BB instance or user's account files are accessed.
- No HTTP server, browser, model prompt, or Claude CLI process is started. The dummy loopback URL in the options is never contacted; the injected upstream transport throws on any request.
- A newer main commit,
454392410d47aaae87c7d1a6a9373d9b3a64e714, was fetched during investigation; its diff contains no changes to either involved plugin. This report remains pinned to the first recorded base.
4. Minimal repeatable reproduction
This reproduces only the repository-owned environment/launch-options combination and bypass behavior. It cannot establish Chrome registration, native-account reachability, model billing identity, or successful pooled inference. The script is newly authored from trusted source, not copied from the issue.
- Obtain the trusted base and install/build its frozen dependencies:
git clone --filter=blob:none --no-checkout https://github.com/get-bb/bb.git bb-4707-base cd bb-4707-base git checkout --detach 7b13e936f10349263cf9f531a0adac7bd5f47642 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build
- Save the complete probe below as
plugins/account-pool/src/issue-4707-repro.ts. It creates only synthetic fixture state. - Run:
pnpm exec tsx plugins/account-pool/src/issue-4707-repro.ts
Complete probe
import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createFakePluginHost } from "@get-bb/plugin-sdk/testing";
import { createAccountPoolPlugin } from "./server.js";
import { buildSessionOptions } from "../../provider-claude-code/src/bridge/session-options.js";
const dataDir = await mkdtemp(join(tmpdir(), "bb-4707-probe-"));
const host = createFakePluginHost({
pluginId: "account-pool",
dataDir,
sdk: {
hosts: { list: async () => [{ id: "probe-host", name: "Probe" }] },
system: { providerStates: async () => ({ providers: [] }) },
plugins: {
list: async () => ({ plugins: [{ id: "account-pool", enabled: true }] }),
},
},
});
async function sessionFor(threadId: string) {
const entries = await host.harness.behavior.resolveProviderEnv(
"claude-code",
{
threadId,
projectId: "probe-project",
hostId: "probe-host",
},
);
const env: Record<string, string> = {};
for (const entry of entries) {
env[entry.name] =
typeof entry.value === "string"
? entry.value
: `http://127.0.0.1:1${entry.value.serverPath}`;
}
return buildSessionOptions(
{
cwd: dataDir,
instructionMode: "append",
permissionMode: "default",
permissionScope: "full",
serviceTier: "default",
workflowsEnabled: false,
chromeEnabled: true,
disable1MContext: false,
sandboxEnabled: false,
},
env,
);
}
try {
await createAccountPoolPlugin({
env: {},
fetch: async () => {
throw new Error("This probe must not contact an upstream service.");
},
})(host.bb);
await host.harness.behavior.callRpc("account.add", {
provider: "claude",
source: { kind: "api-key", apiKey: "probe-only-not-a-real-api-key" },
label: null,
priority: 100,
});
await host.harness.behavior.runCli(["routing", "claude"]);
const pooled = await sessionFor("probe-thread");
assert.deepEqual(pooled.extraArgs, { chrome: null });
assert.ok(pooled.env?.ANTHROPIC_AUTH_TOKEN);
assert.ok(pooled.env?.ANTHROPIC_BASE_URL);
console.log(
JSON.stringify({
mode: "pooled",
chromeFlag: pooled.extraArgs?.chrome === null,
poolAuthPresent: Boolean(pooled.env?.ANTHROPIC_AUTH_TOKEN),
poolBaseUrlPresent: Boolean(pooled.env?.ANTHROPIC_BASE_URL),
}),
);
const bypass = await host.harness.behavior.runCli(["bypass", "probe-thread"]);
assert.equal(bypass.exitCode, 0);
const bypassed = await sessionFor("probe-thread");
assert.deepEqual(bypassed.extraArgs, { chrome: null });
assert.equal(bypassed.env?.ANTHROPIC_AUTH_TOKEN, undefined);
assert.equal(bypassed.env?.ANTHROPIC_BASE_URL, undefined);
console.log(
JSON.stringify({
mode: "bypassed",
chromeFlag: bypassed.extraArgs?.chrome === null,
poolAuthPresent: Boolean(bypassed.env?.ANTHROPIC_AUTH_TOKEN),
poolBaseUrlPresent: Boolean(bypassed.env?.ANTHROPIC_BASE_URL),
}),
);
const other = await sessionFor("other-probe-thread");
assert.ok(other.env?.ANTHROPIC_AUTH_TOKEN);
const revert = await host.harness.behavior.runCli([
"bypass",
"probe-thread",
"--off",
]);
assert.equal(revert.exitCode, 0);
const restored = await sessionFor("probe-thread");
assert.ok(restored.env?.ANTHROPIC_AUTH_TOKEN);
console.log(
JSON.stringify({
otherThreadStillPooled: Boolean(other.env?.ANTHROPIC_AUTH_TOKEN),
revertedThreadPooled: Boolean(restored.env?.ANTHROPIC_AUTH_TOKEN),
chromeToolRegistration: "not-tested",
}),
);
} finally {
await host.harness.lifecycle.dispose();
await rm(dataDir, { recursive: true, force: true });
}
Expected and actual
For the repository-owned contract, expected behavior is: pooling supplies its auth/base URL, Chrome remains enabled, and bypass affects only the selected thread. The actual output in both clean runs is exactly:
{"mode":"pooled","chromeFlag":true,"poolAuthPresent":true,"poolBaseUrlPresent":true}
{"mode":"bypassed","chromeFlag":true,"poolAuthPresent":false,"poolBaseUrlPresent":false}
{"otherThreadStillPooled":true,"revertedThreadPooled":true,"chromeToolRegistration":"not-tested"}
Those assertions pass. For the user-visible browser integration, the desired result is available Chrome tools when explicitly enabled with a usable native login. No actual MCP/tool-count output was collected here. Treating the options probe as evidence of zero Chrome tools would overstate the result.
5. Root cause and causal limits
Verified BB-owned mechanism
- The pool contribution puts its hub credential in the standard Claude auth variable, independently of the Chrome setting:
{ name: "ANTHROPIC_BASE_URL", value: { serverPath: HUB_BASE_PATH }, ... } { name: "ANTHROPIC_AUTH_TOKEN", value: token, ... } - The Chrome flag builder only controls the CLI flag:
return chromeEnabled ? { chrome: null } : undefined;The options builder copies the environment and adds the flag without resolving authentication identity. - The bridge environment merge gives the explicit overrides precedence over inherited host variables. The SDK query options use that environment; the SDK extra-args merge preserves the Chrome argument.
The concrete verified cause is that pooled transport authentication and the requested Chrome launch mode coexist in one Claude process environment; BB does not reconcile them at this boundary. If the downstream CLI requires native authentication for Chrome and rejects that identity while the standard token override is present, this combination explains the symptom. The conditional downstream premise was not tested in this environment. Confidence is high for the BB routing/flag mechanism and medium for the full reported root cause.
Bypass and timing
The pool contribution branch returns no overrides for a bypassed thread when there is no parent pool. The probe verifies the CLI transition and restoration. Parent-pool isolation differs: that branch can return explicit empty routing variables rather than an empty list, so the result must not be generalized to every nested-pool setup.
The provider's turn-environment updater changes the session environment and marks the resident session for rebuild. This report does not dynamically exercise a live turn or steer. Removing the pool override does not prove the host has a valid native login or that browser tools can connect.
User-facing disclosure
The current Chrome setting description states that the extension and a native login are required, but does not mention the Account Pooler conflict or link the per-thread bypass. That text is verified statically; no UI screenshot or visual defect is claimed.
6. Proposed next step and simple-fix decision
Do not remove the pool auth token globally based on this probe. The hub authenticator requires either its custom token header or its bearer credential; dropping authentication without a supported replacement can break pooled requests. The acceptance of a header alone does not establish a safe identity split.
The next decisive experiment is a clean macOS fixture with a disposable native claude.ai login and installed Chrome extension. Observe Chrome MCP initialization and actual pooled message/token-count requests while separating hub authentication from native browser identity. Include hosts with no native login, expired credentials, existing custom headers, and remote execution. Establish which account owns browser/connector identity, inference identity, and local usage checks before choosing an implementation.
Meanwhile, an explicit warning and opt-in per-thread native route can make the limitation understandable without silently changing routing. That remains a product decision: bypass requires local credentials and gives up pooled account selection/failover. The existing scoped bypass is verified only at the options boundary in this report.
No fix branch or PR was created. The rule requires end-to-end reproduction on trusted main before a fix, which is not available here; the proposed root-cause authentication change also crosses a security/product boundary and fails the rule's simple-fix criteria. There is no failing Chrome regression test established by this investigation. Open-PR cross-reference metadata and an open-PR body search for issue 4707 returned no linked PR at investigation time.
7. Verification: second clean checkout, same agent
The same agent repeated the probe in a second clean detached worktree at 7b13e936f10349263cf9f531a0adac7bd5f47642. The tree was clean before adding only the authored probe; there were no production changes. The second run used its own dependency installation and a new fixture data directory. Because the probe starts no listeners, there are no shared ports to collide with. This is a repeatability check, not independent verification by another agent.
git worktree add --detach ../bb-4707-verify 7b13e936f10349263cf9f531a0adac7bd5f47642 cd ../bb-4707-verify pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm exec tsx plugins/account-pool/src/issue-4707-repro.ts pnpm exec turbo run test --filter=bb-plugin-account-pool -- src/server.test.ts -t 'resolves distinct secret machine tokens and honors per-thread bypass' pnpm exec turbo run test --filter=bb-plugin-provider-claude-code -- src/bridge/__tests__/sdk-session.test.ts
| Check | First checkout | Second checkout |
|---|---|---|
| Frozen dependency installation and full Turbo build | Passed: 63/63 build tasks | Passed: 63/63 build tasks |
| Auth/Chrome/bypass executable probe | Exit 0; three JSON records | Exit 0; identical three JSON records |
| Existing pool env and scoped-bypass test | 1 passed; 174 intentionally skipped | 1 passed; 174 intentionally skipped |
| Existing provider SDK-session tests | Not rerun | 7 passed |
| Account Pooler typecheck including the probe | Passed through Turbo | Not rerun |
| Real Chrome MCP initialization/native-login baseline | Not attempted: required fixture unavailable | Not attempted: same limitation |
The second run supported the same partial verdict with no correction to the runtime findings. All cited source lines were checked against the recorded base. No screenshots exist because there was no browser or visual reproduction. The probe cleans its own synthetic data, and no service remains running.
8. Related issues
For triage comparison, repository metadata for #1667 identifies a Feature with Medium priority/effort; #4352 is a Bug with Medium priority/effort. This investigation does not claim to reproduce those issues or establish duplicate equivalence. The current flag builder is present and exercised, so this report is not evidence of a missing --chrome flag.
9. Appendix: evidence and trust boundary
Saved command summaries
First full build:
Tasks: 63 successful, 63 total
Second full build:
Tasks: 63 successful, 63 total
Focused existing pool test, both runs:
Test Files 1 passed (1)
Tests 1 passed | 174 skipped (175)
Existing provider SDK-session suite:
Test Files 1 passed (1)
Tests 7 passed (7)
Account Pooler typecheck:
Tasks: 5 successful, 5 total
The remaining investigation commands were read-only GitHub property/label/release/PR metadata reads, trusted git clone/fetch/worktree operations, focused source inspection, source formatting, and git diff --check. Raw build logs and fixture files are not published. The public reports repository excludes separate scripts/logs, so the complete executable evidence is embedded in this HTML instead of committing an artifact directory.
Issue title, body, links, commands, and proposed patches were treated as untrusted claims. No supplied script was executed, no linked external URL was fetched, and no linked PR branch or runtime was used. All executed application code came from the trusted recorded main commit and the small probe authored from repository evidence. No dependency was added; no user credentials or live runtime state were read or published.
AGENT GENERATED