Summary
WorkflowChatTransport currently treats a client-side AbortController abort as if it were an accidental stream interruption, so it may automatically reconnect and reopen the HTTP stream.
This makes it hard to intentionally detach the client from the HTTP stream while letting the underlying workflow/agent run continue (which is a key resumable-streams use case).
Expected Behavior
When the caller aborts the transport request:
- The current HTTP stream should stop.
- The transport should not auto-reconnect.
- The underlying workflow/agent run should continue (server-side behavior unchanged).
Actual Behavior
- Aborting the client request during the initial POST stream (before a
finish chunk is received) causes WorkflowChatTransport to treat the abort as an interruption and automatically reconnect.
- The reconnect GET path does not propagate the caller’s abort signal.
Why This Matters
This issue affects a valid resumable-streams workflow where the client intentionally disconnects only the HTTP stream (without canceling the underlying workflow run).
The intended flow is:
- The user deliberately detaches from the current stream (for example: closing a panel, navigating away, or pressing a "disconnect" action).
- The workflow/agent keeps running on the server.
- The client reconnects later (manually or automatically) and resumes streaming from the last chunk.
In this flow, step 1 is not a failure and should not trigger an immediate reconnect. WorkflowChatTransport currently treats that intentional client abort like an accidental stream interruption, so it can reconnect right away instead of staying disconnected until the client chooses to resume.
Reproduction (conceptual)
const controller = new AbortController();
const transport = new WorkflowChatTransport({
api: "/api/chat",
onChatEnd: () => {
console.log("chat finished");
},
});
const stream = await transport.sendMessages({
trigger: "submit-message",
chatId: "test-chat",
messages,
abortSignal: controller.signal,
});
const reader = stream.getReader();
// Start reading, then abort intentionally:
reader.read();
controller.abort(); // intended: detach HTTP stream only
Observe:
- The transport may reconnect (
GET /api/chat/{runId}/stream?startIndex=...) instead of staying detached.
Root Cause
In packages/ai/src/workflow-chat-transport.ts:
sendMessagesIterator() catches POST stream errors and (if no "finish" chunk was recorded) falls into reconnect logic.
- Abort is not distinguished from genuine network interruption in a way that preserves "intentional detach" semantics.
reconnectToStreamIterator() reconnect fetch path can drop/ignore the caller abort signal.
Summary
WorkflowChatTransportcurrently treats a client-sideAbortControllerabort as if it were an accidental stream interruption, so it may automatically reconnect and reopen the HTTP stream.This makes it hard to intentionally detach the client from the HTTP stream while letting the underlying workflow/agent run continue (which is a key resumable-streams use case).
Expected Behavior
When the caller aborts the transport request:
Actual Behavior
finishchunk is received) causesWorkflowChatTransportto treat the abort as an interruption and automatically reconnect.Why This Matters
This issue affects a valid resumable-streams workflow where the client intentionally disconnects only the HTTP stream (without canceling the underlying workflow run).
The intended flow is:
In this flow, step 1 is not a failure and should not trigger an immediate reconnect.
WorkflowChatTransportcurrently treats that intentional client abort like an accidental stream interruption, so it can reconnect right away instead of staying disconnected until the client chooses to resume.Reproduction (conceptual)
Observe:
GET /api/chat/{runId}/stream?startIndex=...) instead of staying detached.Root Cause
In
packages/ai/src/workflow-chat-transport.ts:sendMessagesIterator()catches POST stream errors and (if no"finish"chunk was recorded) falls into reconnect logic.reconnectToStreamIterator()reconnect fetch path can drop/ignore the caller abort signal.