From 1f7ec2c3213bb8a160f6c29774a9bdb76b4dff8d Mon Sep 17 00:00:00 2001 From: Ravi Tharuma <25951435+RaviTharuma@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:35:43 +0200 Subject: [PATCH] fix(cpa): isolate credential-pool failures (#8308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cpa): isolate credential pool failures Co-Authored-By: Claude * fix(cpa): forward transport through the chatCore key-health wrapper The local recordKeyHealthStatus wrapper in handleChatCore only declared (status, creds), so the transport argument added for CPA credential-pool isolation was silently dropped at the call site (TS2554 "Expected 2 arguments, but got 3" once chatCore.ts is typechecked with tsc directly — this file is not in tsconfig.typecheck-core.json's file list, so `npm run typecheck:core` did not surface it). The CPA isolation guard in keyHealth.ts never received `transport`, so it never fired. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Ravi Tharuma Co-authored-by: Claude Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- open-sse/executors/cliproxyapi.ts | 2 +- open-sse/handlers/chatCore.ts | 11 ++++++++--- open-sse/handlers/chatCore/keyHealth.ts | 7 ++++++- open-sse/handlers/chatCore/upstreamTimeouts.ts | 10 +++++++++- tests/unit/chatcore-key-health.test.ts | 6 ++++++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/open-sse/executors/cliproxyapi.ts b/open-sse/executors/cliproxyapi.ts index 07bece15c0..83095f4d20 100644 --- a/open-sse/executors/cliproxyapi.ts +++ b/open-sse/executors/cliproxyapi.ts @@ -428,7 +428,7 @@ export class CliproxyapiExecutor extends BaseExecutor { input.log?.warn?.("CPA", `CLIProxyAPI rate limited: ${response.status}`); } - return { response, url, headers, transformedBody }; + return { response, url, headers, transformedBody, transport: "cliproxyapi" as const }; } /** diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 78cc5c25cb..368cb70095 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -549,8 +549,9 @@ export async function handleChatCore({ // and delegate so the existing call sites stay byte-identical. const recordKeyHealthStatus = ( status: number, - creds: Record | null | undefined - ): void => recordKeyHealthStatusFor(status, creds, log); + creds: Record | null | undefined, + transport?: string + ): void => recordKeyHealthStatusFor(status, creds, log, transport); const persistCodexQuotaState = async (headers: Record | null, status = 0) => { const currentConnectionId = getCurrentConnectionId(); @@ -2988,7 +2989,11 @@ export async function handleChatCore({ rawResult._executionCredentials?.connectionId && rawResult._executionCredentials?.apiKey ) { - recordKeyHealthStatus(status, rawResult._executionCredentials); + recordKeyHealthStatus( + status, + rawResult._executionCredentials, + rawResult.transport + ); } releaseRawResultAccountSemaphore = typeof rawResult._accountSemaphoreRelease === "function" diff --git a/open-sse/handlers/chatCore/keyHealth.ts b/open-sse/handlers/chatCore/keyHealth.ts index 100185b442..a3d6949651 100644 --- a/open-sse/handlers/chatCore/keyHealth.ts +++ b/open-sse/handlers/chatCore/keyHealth.ts @@ -31,8 +31,13 @@ type KeyHealthLog = { export function recordKeyHealthStatus( status: number, creds: Record | null | undefined, - log?: KeyHealthLog + log?: KeyHealthLog, + transport?: string ): void { + // CLIProxyAPI owns a shared external credential pool. Its auth failures cannot be + // attributed to the native OmniRoute connection selected before proxy dispatch. + if (transport === "cliproxyapi") return; + const connId = creds?.connectionId as string | undefined; if (!connId) return; diff --git a/open-sse/handlers/chatCore/upstreamTimeouts.ts b/open-sse/handlers/chatCore/upstreamTimeouts.ts index 0c63c53346..551b952e2c 100644 --- a/open-sse/handlers/chatCore/upstreamTimeouts.ts +++ b/open-sse/handlers/chatCore/upstreamTimeouts.ts @@ -106,8 +106,15 @@ export function normalizeExecutorResult( url?: string; headers?: Record; transformedBody?: unknown; + transport?: string; } -): { response: Response; url: string; headers: Record; transformedBody: unknown } { +): { + response: Response; + url: string; + headers: Record; + transformedBody: unknown; + transport?: string; +} { if (result instanceof Response) { return { response: result, url: "", headers: {}, transformedBody: null }; } @@ -116,6 +123,7 @@ export function normalizeExecutorResult( url: result.url || "", headers: result.headers || {}, transformedBody: result.transformedBody ?? null, + transport: result.transport, }; } diff --git a/tests/unit/chatcore-key-health.test.ts b/tests/unit/chatcore-key-health.test.ts index 28e3e2c8c8..bcb1167997 100644 --- a/tests/unit/chatcore-key-health.test.ts +++ b/tests/unit/chatcore-key-health.test.ts @@ -68,3 +68,9 @@ test("non-401 / non-2xx status does not touch key health", () => { recordKeyHealthStatus(500, creds(conn), noopLog); assert.equal(getAllKeyHealth()[`${conn}:primary`], undefined); }); + +test("CPA pool failures do not poison the selected native connection key", () => { + const conn = "kh-cpa-pool-isolation"; + recordKeyHealthStatus(401, creds(conn), noopLog, "cliproxyapi"); + assert.equal(getAllKeyHealth()[`${conn}:primary`], undefined); +});