fix(streaming): detect premature connection close and surface error to user
When a reverse proxy (e.g., Tailscale Serve, nginx) closes the SSE
connection after an idle timeout, the reader returns { done: true }
cleanly. Previously this was treated as a successful completion, causing
the thinking indicator to disappear and a retry button to appear with
no error message — silently discarding any in-progress backend work.
Now: if the stream ends without a 'done' event AND no response text was
received while in 'accepted' or 'active' phase, call markFailed() with
a clear message instead of finishStream(). This surfaces an error toast
and persists the error state so the user knows what happened.
Fixes #512.
Validation: pnpm build passes, use-streaming-message tests (4/4) pass.
This commit is contained in:
@@ -983,7 +983,21 @@ export function useStreamingMessage(options: UseStreamingMessageOptions = {}) {
|
||||
|
||||
const lifecyclePhase = lifecyclePhaseRef.current as StreamLifecyclePhase
|
||||
if (!finishedRef.current && lifecyclePhase !== 'handoff') {
|
||||
finishStream()
|
||||
// If the stream ended cleanly (no 'done' event) but we never received
|
||||
// any response text, treat it as a failure rather than a successful
|
||||
// empty completion. This happens when a proxy (e.g., Tailscale Serve)
|
||||
// closes the connection after an idle timeout — the reader returns
|
||||
// { done: true } but the model was still generating. Fixes #512.
|
||||
if (
|
||||
!fullTextRef.current &&
|
||||
(lifecyclePhase === 'accepted' || lifecyclePhase === 'active')
|
||||
) {
|
||||
markFailed(
|
||||
'Connection closed before response was received. The backend may still be processing — check server logs or retry.',
|
||||
)
|
||||
} else {
|
||||
finishStream()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if ((err as Error).name === 'AbortError') {
|
||||
|
||||
Reference in New Issue
Block a user