diff --git a/src/lib/gateway-chat-pool.test.ts b/src/lib/gateway-chat-pool.test.ts index 493776b..273ffb1 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 2263ec2..cf469c4 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",