From 49dc5627a0abb71841584a3dbf877297b403a6c1 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Mon, 25 May 2026 05:11:55 +1000 Subject: [PATCH] fix: reconnect disconnected held gateway clients --- src/lib/gateway-chat-pool.test.ts | 19 +++++++++++++++++++ src/lib/gateway-chat-pool.ts | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib/gateway-chat-pool.test.ts b/src/lib/gateway-chat-pool.test.ts index 493776bd..273ffb17 100644 --- a/src/lib/gateway-chat-pool.test.ts +++ b/src/lib/gateway-chat-pool.test.ts @@ -187,4 +187,23 @@ describe("getGatewayClientForRuntime", () => { expect(mockState.clients[0].close).toHaveBeenCalledTimes(1); expect(getGatewayPoolDiagnostics()).toMatchObject({ poolSize: 1 }); }); + + it("replaces disconnected held clients instead of returning them", async () => { + addRuntime("runtime-a"); + + const disconnectedClient = await getGatewayClientForRuntime("runtime-a"); + holdClient(disconnectedClient); + mockState.clients[0].isConnected = false; + + const freshClient = await getGatewayClientForRuntime("runtime-a"); + + expect(freshClient).not.toBe(disconnectedClient); + expect(mockState.clients).toHaveLength(2); + expect(mockState.clients[0].close).toHaveBeenCalledTimes(1); + expect(getGatewayPoolDiagnostics()).toMatchObject({ + activeClients: 0, + activeHolds: 0, + poolSize: 1, + }); + }); }); diff --git a/src/lib/gateway-chat-pool.ts b/src/lib/gateway-chat-pool.ts index 2263ec26..cf469c4d 100644 --- a/src/lib/gateway-chat-pool.ts +++ b/src/lib/gateway-chat-pool.ts @@ -505,7 +505,7 @@ async function getClientForRuntime(runtime: GatewayRuntimeConnection | null | un // Close stale connection — but only if no active request is using it if (existing) { - if (activeClientHolds.has(existing.client)) { + if (activeClientHolds.has(existing.client) && existing.client.isConnected) { // Client is mid-request, return it anyway (don't recycle under its feet) publishAgentModeDiagnostic({ scope: "gateway-pool", @@ -519,6 +519,7 @@ async function getClientForRuntime(runtime: GatewayRuntimeConnection | null | un }); return existing.client; } + activeClientHolds.delete(existing.client); publishAgentModeDiagnostic({ scope: "gateway-pool", event: "client.recycle.close-stale",