From fe57d7e952d8ec679821c3959b24171b042fe09b Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 28 Nov 2025 19:39:49 +0000 Subject: [PATCH] fix: proper stream cleanup and transport close on disconnect 1. createWebReadableStream: Add closed flag and cancel() callback to prevent double-close crashes when SSE streams end. This fixes the "Controller is already closed" error that occurs when the SDK calls response.body.cancel() on 202 responses. 2. DELETE handler: Call serverTransport.close() after terminateSession() to properly cleanup the transport and cancel pending reconnections. - terminateSession() notifies the server (sends DELETE request) - close() cleans up client-side resources (abort controller, timeouts) - Both are needed for a complete disconnect --- server/src/index.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 88954ebc5..2680f22e5 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -241,18 +241,31 @@ const authMiddleware = ( * This is necessary for the EventSource polyfill which expects web streams */ const createWebReadableStream = (nodeStream: any): ReadableStream => { + let closed = false; return new ReadableStream({ start(controller) { nodeStream.on("data", (chunk: any) => { - controller.enqueue(chunk); + if (!closed) { + controller.enqueue(chunk); + } }); nodeStream.on("end", () => { - controller.close(); + if (!closed) { + closed = true; + controller.close(); + } }); nodeStream.on("error", (err: any) => { - controller.error(err); + if (!closed) { + closed = true; + controller.error(err); + } }); }, + cancel() { + closed = true; + nodeStream.destroy(); + }, }); }; @@ -526,6 +539,7 @@ app.delete( res.status(404).end("Transport not found for sessionId " + sessionId); } else { await serverTransport.terminateSession(); + await serverTransport.close(); webAppTransports.delete(sessionId); serverTransports.delete(sessionId); sessionHeaderHolders.delete(sessionId);