Skip to content

fix(account-pool): isolate upstream connections from shared HTTP/2 sessions - #3203

Open
hemaaanth wants to merge 2 commits into
get-bb:mainfrom
hemaaanth:fix/account-pool-destroyed-session
Open

fix(account-pool): isolate upstream connections from shared HTTP/2 sessions#3203
hemaaanth wants to merge 2 commits into
get-bb:mainfrom
hemaaanth:fix/account-pool-destroyed-session

Conversation

@hemaaanth

@hemaaanth hemaaanth commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Human comments

What was wrong

Account Pooler used the process-wide fetch dispatcher for upstream requests. In #3201, a cached upstream HTTP/2 session was destroyed but continued failing requests immediately with TypeError: fetch failed, caused by The session has been destroyed (ERR_HTTP2_INVALID_SESSION). This was observed for both the Anthropic (api.anthropic.com) and ChatGPT/Codex (chatgpt.com) origins on the same host: whichever origin's cached session was destroyed failed every request to it, while the other origin could remain healthy. Clearing the affected origin's cached pool restored a real pooled request. The hub discarded the fetch cause, leaving only a generic 502 and obscuring the transport failure. The event that originally destroyed the session remains unknown.

What changed

Give Account Pooler an owned EnvHttpProxyAgent with HTTP/2 disabled, covering proxied inference, token refresh, usage requests, and OAuth login. Because this transport is used for every upstream, the fix applies uniformly to all providers (Claude and ChatGPT/Codex), for both the plain HTTP path and the Codex realtime path, which forwards its upstream leg through the same hub transport. It honors proxy environment variables and avoids reuse of the process-wide HTTP/2 pool. Disposal waits for the hub's existing request-drain window before destroying connections. No request retries are added.

Retain the original fetch cause internally and log only a recognized transport error code, without raw error messages or credentials. Document the transport behavior in PLUGIN_OVERVIEW.md. There are no CLI or server/daemon wire changes.

Trade-offs

  • Upstream traffic now uses HTTP/1.1 instead of HTTP/2. This is the mechanism that avoids reusing a poisoned shared H2 session, and undici's HTTP/1.1 pool discards dead sockets rather than caching a destroyed session. The cost is losing H2 multiplexing: under high concurrency the pooler opens more connections. Both providers support HTTP/1.1 including streaming, so this is an efficiency trade, not a correctness one.
  • This isolates the pooler from the shared dispatcher and avoids the observed failure mode. It does not identify or fix the underlying trigger that destroys a session, and it adds no retry, so a connection that dies mid-request still fails that request.

How you verified

  • pnpm exec turbo run test typecheck --filter=bb-plugin-account-pool: 261 tests passed across 10 files; typecheck passed.
  • pnpm exec turbo run build --filter=@bb/server: passed. Scoped Oxlint and git diff --check passed.
  • Regression coverage injects the observed destroyed-session error at the default-fetch boundary and verifies that the owned transport completes actual local requests. A committed TLS/ALPN test also verifies HTTP/1.1 against a server offering both protocols; temporarily enabling HTTP/2 makes it fail with h2 / 2.0, as expected. The natural session-destruction trigger remains unknown.
  • A streamed-response disposal regression failed before adding the drain wait and passes after it. Additional tests cover cancellation, no replay after an accepted POST loses its connection, and sanitized logging.
  • All six transport tests, including TLS/ALPN negotiation, also passed directly under Node 22.23.2 as a compatibility investigation. The full plugin suite ran on Node 26.7.0. Local transport tests bypass ambient proxies.
  • Cross-provider transport check: unauthenticated requests through the new transport reached both https://api.anthropic.com and https://chatgpt.com, each returning the expected HTTP 401 rather than the destroyed-session error, confirming the fix covers both providers. The installed BB runtime was not replaced with this build.

Fixes #3201

AGENT GENERATED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Account Pooler: persistent immediate 502s from a destroyed transport session (Claude and ChatGPT/Codex); underlying cause is discarded

1 participant