diff --git a/packages/gateway-client/src/client.ts b/packages/gateway-client/src/client.ts index 2aa0dfce9b4f..55273f0cf26b 100644 --- a/packages/gateway-client/src/client.ts +++ b/packages/gateway-client/src/client.ts @@ -1599,7 +1599,14 @@ export class GatewayClient { }); signal?.addEventListener("abort", abortHandler, { once: true }); }); - this.ws.send(JSON.stringify(frame)); + try { + this.ws.send(JSON.stringify(frame)); + } catch (error) { + const pending = this.pending.get(id); + this.pending.delete(id); + pending?.cleanup?.(); + throw error; + } return p; } } diff --git a/packages/gateway-client/src/client.watchdog.test.ts b/packages/gateway-client/src/client.watchdog.test.ts index 4d3b0e8a642a..2e565b2b7029 100644 --- a/packages/gateway-client/src/client.watchdog.test.ts +++ b/packages/gateway-client/src/client.watchdog.test.ts @@ -317,6 +317,27 @@ describe("GatewayClient", () => { } }); + test("cleans pending request state when websocket send throws", async () => { + const client = new GatewayClient({ + requestTimeoutMs: 25, + }); + const sendError = new Error("synthetic send failure"); + ( + client as unknown as { + ws: WebSocket | { readyState: number; send: () => void; close: () => void }; + } + ).ws = { + readyState: WebSocket.OPEN, + send: vi.fn(() => { + throw sendError; + }), + close: vi.fn(), + }; + + await expect(client.request("status")).rejects.toThrow("synthetic send failure"); + expect(getPendingCount(client)).toBe(0); + }); + test("does not auto-timeout expectFinal requests", async () => { vi.useFakeTimers(); try {