Problem
An answer stops mid-sentence. The viewer can’t tell whether the connection dropped, the model failed, a quota ran out, or the response is simply still coming, and the partial text sitting on screen looks identical in all four cases.
Solution
Name the failure and keep what arrived. Both halves matter, and products routinely ship one.
Keeping the partial response is the part most often wrong. Wiping the turn on failure discards work the viewer could use, and where a response had streamed four paragraphs before dying, three of them were probably fine. Leave them, mark the turn as incomplete, and make sure the incompleteness travels with the text when it’s copied or when the model reads the history on the next turn.
Naming the failure means distinguishing cases that call for different responses. A network interruption is the viewer’s to retry. An overloaded model is worth retrying in a moment. An exhausted quota won’t improve by retrying and belongs to the usage meter. A content filter stopping mid-stream is a refusal wearing an error’s clothes, and treating it as a transient fault sends the viewer into a retry loop that can’t succeed. NN/g’s guidance on error messages applies unchanged here: say what happened in plain language, and say what to do about it.
Retry needs to be the obvious next control, and it needs to be honest about what it does. Resuming a stream from where it stopped is rarely possible, so retry usually means regenerating from the start, and the viewer shouldn’t be surprised when paragraph one comes back different. Where the failure was transient, an automatic single retry before showing anything at all removes most of these events from the viewer’s experience entirely.
The client side is unglamorous and decides how this feels. An aborted fetch, a closed event stream and a silently stalled connection are three different conditions, and the third is the one that hangs an interface. A timeout on the gap between deltas catches a stream that stopped arriving without closing, which is the failure that otherwise leaves a spinner running forever.
Distinguishing this state from stop generation is worth explicit design. The two produce almost identical screens, a truncated answer with a control beneath it, and they mean opposite things: one is the viewer’s decision—the other is the product failing. A viewer who can’t tell them apart concludes the product is broken every time they interrupt something.
Use when
Responses stream, which is to say always.
Don’t use when
The system declined rather than failed. A decline is a refusal, and dressing a policy decision up as a technical fault sends people to retry when they should be rephrasing.
Trade-offs
Keeping partial output preserves value and leaves a truncated answer in the permanent record that later turns will read as context. Specific error messages are far more useful and leak information about infrastructure, which is why so many products fall back to a generic apology that helps nobody. Automatic retry hides transient failures and doubles the cost of a request that was going to fail anyway. Telling an error apart from a stop requires two visual states for what’s physically the same screen.
Checklist
- Does the partial response survive the failure?
- Is the turn marked incomplete, and does that mark travel with the text?
- Are transient failures, quota exhaustion, and content stops distinguished?
- Does the message say what to do next?
- Is there a timeout on the gap between deltas, or only on the request?
- Does retry regenerate from the start, and does the viewer know that?
- Is a transient failure retried once automatically before anything is shown?
- Does the model see that the previous turn failed?
- Can this state be told apart from a deliberate stop?
- What happens to a tool call that was in flight?
Compare
ChatGPT leaves the partial text in place with a retry control attached to the turn, so a failure is recoverable in one click without losing what arrived. Claude distinguishes overload from other failures explicitly, which tells the viewer whether waiting is a strategy or a waste of time. Perplexity fails earlier in the pipeline more often, at the search step rather than the generation step, so its equivalent state is about sources rather than a truncated answer. GitHub Copilot mostly fails silently and produces no suggestion at all. Invisible is right for an ambient feature. It also means a persistent outage can go unnoticed for a long time.
Related
Streaming response is the mechanism whose failure this describes. Stop generation produces a nearly identical screen for an opposite reason. Refusal is the case where nothing broke. Usage meter owns the quota version of this state. Tool call trace is where a mid-chain failure becomes visible.