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.C

View on GitHub (pinned to e2d772072e)

Solutions

  1. Retry the request; if reproducible, reduce prompt/output size
  2. Inspect provider/proxy logs for upstream disconnects or content-filter aborts
  3. Check LiteLLM proxy version and logs if streaming through it
  4. Use the partial accumulated result as a degraded response when acceptable
  5. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/4464fafe6e29dfc4. Report an issue: GitHub.