Bug Description
When resumableStream is enabled on a chat request, the server-core handler unconditionally sets options.abortSignal = undefined, making it impossible for clients to cancel an in-progress generation. Clicking "Stop" in the UI only stops the client-side stream display — the backend LLM continues running to completion.
Root Cause
In packages/server-core/src/handlers/agent.handlers.ts:
```typescript
if (resumableStreamEnabled) {
options.abortSignal = undefined; // <-- AbortSignal is discarded
}
```
The comment indicates this was intentional (to support reconnection), but it has the side effect of making cancellation completely non-functional when resumable streams are in use.
Expected Behavior
Clients should be able to cancel an in-progress LLM generation even when resumableStream: true is set. The two concerns (resumability and cancellability) should be independent.
Suggested Fix
Instead of discarding the AbortSignal, the handler could:
-
Use a composite signal — create an internal AbortController for the LLM call, and abort it when either the client's signal fires or a new cancel API endpoint is called. The resumable stream adapter can then clean up independently.
-
Expose a cancel endpoint — add DELETE /agents/:id/chat/:conversationId (similar to POST /workflows/:id/executions/:executionId/cancel for workflows) that aborts the active generation and calls clearActiveStream.
Either approach decouples cancellation from the stream transport layer.
Steps to Reproduce
- Enable resumable streams: send a chat request with
options.resumableStream: true
- Start a long-running generation
- Call the stop/abort action (client-side
AbortController.abort() or disconnect)
- Observe: the backend LLM generation continues until completion
Environment
- VoltAgent server-core (checked against current
main)
- Reproduces with any LLM provider (OpenAI, Anthropic, etc.)
Related Code
packages/server-core/src/handlers/agent.handlers.ts — where abortSignal is cleared
packages/server-core/src/utils/options.ts — where the signal is initially set
packages/server-core/src/handlers/workflow.handlers.ts — workflows already have a proper cancel endpoint as a reference implementation
Bug Description
When
resumableStreamis enabled on a chat request, the server-core handler unconditionally setsoptions.abortSignal = undefined, making it impossible for clients to cancel an in-progress generation. Clicking "Stop" in the UI only stops the client-side stream display — the backend LLM continues running to completion.Root Cause
In
packages/server-core/src/handlers/agent.handlers.ts:```typescript
if (resumableStreamEnabled) {
options.abortSignal = undefined; // <-- AbortSignal is discarded
}
```
The comment indicates this was intentional (to support reconnection), but it has the side effect of making cancellation completely non-functional when resumable streams are in use.
Expected Behavior
Clients should be able to cancel an in-progress LLM generation even when
resumableStream: trueis set. The two concerns (resumability and cancellability) should be independent.Suggested Fix
Instead of discarding the
AbortSignal, the handler could:Use a composite signal — create an internal
AbortControllerfor the LLM call, and abort it when either the client's signal fires or a new cancel API endpoint is called. The resumable stream adapter can then clean up independently.Expose a cancel endpoint — add
DELETE /agents/:id/chat/:conversationId(similar toPOST /workflows/:id/executions/:executionId/cancelfor workflows) that aborts the active generation and callsclearActiveStream.Either approach decouples cancellation from the stream transport layer.
Steps to Reproduce
options.resumableStream: trueAbortController.abort()or disconnect)Environment
main)Related Code
packages/server-core/src/handlers/agent.handlers.ts— whereabortSignalis clearedpackages/server-core/src/utils/options.ts— where the signal is initially setpackages/server-core/src/handlers/workflow.handlers.ts— workflows already have a propercancelendpoint as a reference implementation