From a8d90f8121560bf61b643455aeffc6325f6bc533 Mon Sep 17 00:00:00 2001 From: Jefsky Agent Date: Thu, 14 May 2026 00:07:00 +0800 Subject: [PATCH] fix(server-core): preserve abortSignal when resumable streams are enabled When resumableStream is enabled, the previous code unconditionally set options.abortSignal = undefined before streamText(), making it impossible for clients to cancel an in-progress generation. The fix: - Saves the client's abortSignal before clearing it - Makes clearActiveStream fire-and-forget (no await) so client abort cannot block handler cleanup - Restores the signal before calling streamText so cancellation works Fixes VoltAgent/voltagent#1239 Co-authored-by: Jefsky Agent --- .../src/handlers/agent.handlers.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/server-core/src/handlers/agent.handlers.ts b/packages/server-core/src/handlers/agent.handlers.ts index 61d28e78f..dc42fd319 100644 --- a/packages/server-core/src/handlers/agent.handlers.ts +++ b/packages/server-core/src/handlers/agent.handlers.ts @@ -314,21 +314,25 @@ export async function handleChatStream( ); } - if (resumableStreamEnabled) { - options.abortSignal = undefined; - } - - options.resumableStream = resumableStreamEnabled; - const resumableStreamAdapter = deps.resumableStream; - if (resumableStreamEnabled && resumableStreamAdapter && conversationId && userId) { - try { - await resumableStreamAdapter.clearActiveStream({ conversationId, agentId, userId }); - } catch (error) { - logger.warn("Failed to clear active resumable stream", { error }); + + if (resumableStreamEnabled) { + // Preserve abortSignal so client can still cancel the LLM stream. + const clientSignal = options.abortSignal; + + // Make clearActiveStream fire-and-forget — don't await it on the hot path, + // and don't let client abort block this cleanup. + if (resumableStreamAdapter && conversationId && userId) { + resumableStreamAdapter.clearActiveStream({ conversationId, agentId, userId }).catch((error) => { + logger.warn("Failed to clear active resumable stream", { error }); + }); } + + // Restore signal so streamText can still be cancelled by the client. + options.abortSignal = clientSignal; } + options.resumableStream = resumableStreamEnabled; const result = await agent.streamText(input, options); let activeStreamId: string | null = null;