WorkflowChatTransport.reconnectToStreamIterator catches every error from the chunk stream and calls console.error('Error in chat GET reconnectToStream', error) unconditionally, including when the error is an AbortError from an intentional client abort (Stop button, tab close, page navigation). The abort is otherwise handled correctly (status transitions back to ready, no leaks), but the browser console fills with what look like real errors on every successful stop.
The same pattern exists for the POST path: console.error('Error in chat POST stream', error).
Versions
workflow@5.0.0-beta.4-30cc3a9
@workflow/ai@5.0.0-beta.3-30cc3a9
ai@6.0.141
- Next.js 16, React 19
Repro
App uses a custom transport over WorkflowChatTransport with a fetch wrapper that combines the user's stop AbortController with the per-request abort signal. When the user clicks Stop or closes the tab while a stream is open, the abort propagates correctly and useChat transitions out of streaming, but the console shows:
Error in chat GET reconnectToStream AbortError: BodyStreamBuffer was aborted
at b67ef0a7d90ce1c7.js:1:28872
at t (5c038c7ff70e563c.js:279:13938)
at sX (1a7dc033a655b4da.js:1:155155)
...
at pull
at setTimeout
Up to maxConsecutiveErrors repetitions per stop (default 3) before the iterator gives up and exits.
Location
packages/ai/src/workflow-chat-transport.ts, inside reconnectToStreamIterator, the catch block around the for await (const chunk of streamToIterator(chunkStream)) loop. In the published dist this is node_modules/@workflow/ai/dist/workflow-chat-transport.js:242:
catch (error) {
console.error('Error in chat GET reconnectToStream', error);
consecutiveErrors++;
if (consecutiveErrors >= this.maxConsecutiveErrors) {
throw new Error(
`Failed to reconnect after ${this.maxConsecutiveErrors} consecutive errors. Last error: ${getErrorMessage(error)}`
);
}
}
Same shape on the POST path around line 129.
Canonical pattern in vercel/ai
The base AI SDK does not log inside its transport. HttpChatTransport.reconnectToStream simply throws on error and delegates error policy to the consumer. The consumer is Chat in packages/ai/src/ui/chat.ts, which has the established AbortError handling at lines 651-666 and 734-740:
activeResponse.abortController.signal.addEventListener('abort', () => {
isAbort = true;
});
...
catch (err) {
// Ignore abort errors as they are expected.
if (isAbort || (err as any).name === 'AbortError') {
isAbort = true;
this.setStatus({ status: 'ready' });
return null;
}
// real error handling
}
WorkflowChatTransport breaks this convention by swallowing the error inside its own iterator and emitting console.error for it, so the Chat class never gets a chance to special-case the abort.
Suggested fix
Either of these works:
1. Guard the existing log.
catch (error) {
const isAbort =
(error as any)?.name === 'AbortError' ||
options.abortSignal?.aborted === true;
if (!isAbort) {
console.error('Error in chat GET reconnectToStream', error);
consecutiveErrors++;
if (consecutiveErrors >= this.maxConsecutiveErrors) {
throw new Error(...);
}
} else {
// Intentional abort: exit the loop cleanly.
return;
}
}
2. Mirror vercel/ai's pattern more directly. Detect abort and return without logging or counting toward consecutiveErrors. This also avoids the misleading Failed to reconnect after N consecutive errors rethrow when the only errors were aborts.
Same treatment is needed for Error in chat POST stream on the send path.
Why this matters
For apps that wire a real Stop button, or rely on tab close to terminate the stream, this log fires on every successful stop. It looks like a regression to the user, creates noise during real debugging, and a strict CI that asserts no console.error will fail.
Related issues and PRs
Happy to send a PR if useful. Otherwise flagging here in case #1847 can pick this up while that area is being rewritten.
WorkflowChatTransport.reconnectToStreamIteratorcatches every error from the chunk stream and callsconsole.error('Error in chat GET reconnectToStream', error)unconditionally, including when the error is anAbortErrorfrom an intentional client abort (Stop button, tab close, page navigation). The abort is otherwise handled correctly (status transitions back toready, no leaks), but the browser console fills with what look like real errors on every successful stop.The same pattern exists for the POST path:
console.error('Error in chat POST stream', error).Versions
workflow@5.0.0-beta.4-30cc3a9@workflow/ai@5.0.0-beta.3-30cc3a9ai@6.0.141Repro
App uses a custom transport over
WorkflowChatTransportwith afetchwrapper that combines the user's stopAbortControllerwith the per-request abort signal. When the user clicks Stop or closes the tab while a stream is open, the abort propagates correctly anduseChattransitions out ofstreaming, but the console shows:Up to
maxConsecutiveErrorsrepetitions per stop (default 3) before the iterator gives up and exits.Location
packages/ai/src/workflow-chat-transport.ts, insidereconnectToStreamIterator, the catch block around thefor await (const chunk of streamToIterator(chunkStream))loop. In the published dist this isnode_modules/@workflow/ai/dist/workflow-chat-transport.js:242:Same shape on the POST path around line 129.
Canonical pattern in
vercel/aiThe base AI SDK does not log inside its transport.
HttpChatTransport.reconnectToStreamsimply throws on error and delegates error policy to the consumer. The consumer isChatinpackages/ai/src/ui/chat.ts, which has the established AbortError handling at lines 651-666 and 734-740:WorkflowChatTransportbreaks this convention by swallowing the error inside its own iterator and emittingconsole.errorfor it, so theChatclass never gets a chance to special-case the abort.Suggested fix
Either of these works:
1. Guard the existing log.
2. Mirror
vercel/ai's pattern more directly. Detect abort and return without logging or counting towardconsecutiveErrors. This also avoids the misleadingFailed to reconnect after N consecutive errorsrethrow when the only errors were aborts.Same treatment is needed for
Error in chat POST streamon the send path.Why this matters
For apps that wire a real Stop button, or rely on tab close to terminate the stream, this log fires on every successful stop. It looks like a regression to the user, creates noise during real debugging, and a strict CI that asserts no
console.errorwill fail.Related issues and PRs
WorkflowChatTransport treats client abort as reconnectable interruption, fixed by threadingabortSignalthroughreconnectToStreamIterator. The reconnect-correctness piece landed there. The log-on-abort side effect was not part of that fix.reconnectToStreamIteratorfunction.getReadablelevel. If that lands, it might be the natural place to absorb this fix.BodyStreamBuffer was abortedai#1445, fix(swc-plugin): preserve step function names in stack traces after bundler minification #1743, #9707, #10719 — the broaderBodyStreamBuffer was abortedanduseChat.stop()family in the AI SDK side. fix(swc-plugin): preserve step function names in stack traces after bundler minification #1743 explicitly notes this class of error is expected on stop and should be silently handled.Happy to send a PR if useful. Otherwise flagging here in case #1847 can pick this up while that area is being rewritten.