plandex-ai/plandex · error
model stream ended unexpectedly: %w
Error message
model stream ended unexpectedly: %w
What it means
stream.Recv() returned io.EOF before the stream was marked finished (no finish-reason/usage chunk had arrived). The library treats a clean EOF at this point as an abnormal termination: the model closed the stream without a proper completion signal. The error is attached to the accumulated partial result and also returned.
Source
Thrown at app/server/model/client_stream.go:143
log.Println("Stream canceled")
return accumulator.Result(true, streamCtx.Err()), streamCtx.Err()
case <-timer.C:
log.Println("Stream timed out due to inactivity")
if streamFinished {
log.Println("Stream finished—timed out waiting for usage chunk")
return accumulator.Result(false, nil), nil
} else {
log.Println("Stream timed out due to inactivity")
return accumulator.Result(true, fmt.Errorf("stream timed out due to inactivity. The model is not responding.")), nil
}
default:
response, err := stream.Recv()
if err == io.EOF {
if streamFinished {
return accumulator.Result(false, nil), nil
}
err = fmt.Errorf("model stream ended unexpectedly: %w", err)
return accumulator.Result(true, err), err
}
if err != nil {
err = fmt.Errorf("error receiving stream chunk: %w", err)
return accumulator.Result(true, err), err
}
if response.ID != "" {
accumulator.SetGenerationId(response.ID)
}
if !receivedFirstChunk {
receivedFirstChunk = true
accumulator.SetFirstTokenAt(time.Now())
}
if !timer.Stop() {
<-timer.CView on GitHub (pinned to e2d772072e)
Solutions
- Retry the request; if reproducible, reduce prompt/output size
- Inspect provider/proxy logs for upstream disconnects or content-filter aborts
- Check LiteLLM proxy version and logs if streaming through it
- Use the partial accumulated result as a degraded response when acceptable
- Bypass intermediaries (proxy/LB) to confirm whether the provider or the hop is closing the stream
Example fix
// before
result, err := call()
// after: handle premature EOF gracefully
result, err := call()
if err != nil && strings.Contains(err.Error(), "model stream ended unexpectedly") {
if len(result.Content) > 0 { /* accept partial content */ } else { result, err = call() }
} Defensive patterns
Strategy: fallback
Type guard
func isPrematureEOF(err error) bool { return err != nil && strings.Contains(err.Error(), "model stream ended unexpectedly") } Try / catch
result, err := call()
if isPrematureEOF(err) {
if partialContentUsable(result) { return result, nil }
return retryOrFallbackModel()
} Prevention
- Keep intermediaries (LiteLLM, LBs) configured with generous stream idle timeouts
- Cap prompt/output sizes so providers are less likely to abort mid-stream
- Use the accumulator's partial result as a degraded-mode answer
- Track premature-EOF rates per provider to detect upstream instability
When it happens
Trigger: Provider closes the connection after sending some chunks but before a chunk with FinishReason (and the expected usage chunk) arrives; Recv returns io.EOF with streamFinished == false.
Common situations: Provider-side truncation (content filter, max output length enforced at proxy), LiteLLM proxy dropping the upstream connection, load balancer idle cutoff, provider aborting the request server-side.
Related errors
- error creating chat completion stream: %w
- stream timed out due to inactivity. The model is not respond
- error receiving stream chunk: %w
- model stopped with error status | The model is not respondin
- error building validate loop: %w
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/4464fafe6e29dfc4.
Report an issue: GitHub.