siyuan-note/siyuan · error
response stream ended before a terminal event
Error message
response stream ended before a terminal event
What it means
OpenAICompletionStream adapts a Responses-API SSE stream into Chat Completions chunks. A compliant stream must terminate with a terminal event (response.completed, response.incomplete, or a failure event) that carries the final state. If the underlying stream returns io.EOF before any terminal event, the accumulated deltas have no confirmed final state, so Recv reports this error instead of pretending the response ended normally.
Source
Thrown at kernel/util/openai_completion.go:403
return stream.chat.Recv()
}
if stream.responses == nil {
return openai.ChatCompletionStreamResponse{}, io.EOF
}
if len(stream.pending) > 0 {
response := stream.pending[0]
stream.pending = stream.pending[1:]
return response, nil
}
if stream.responsesDone {
return openai.ChatCompletionStreamResponse{}, io.EOF
}
for {
event, err := stream.responses.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
return openai.ChatCompletionStreamResponse{}, errors.New("response stream ended before a terminal event")
}
return openai.ChatCompletionStreamResponse{}, err
}
response := openai.ChatCompletionStreamResponse{Object: "chat.completion.chunk"}
switch event.Type {
case openai.ResponseStreamEventOutputTextDelta:
stream.responseContent.WriteString(event.Delta)
response.Choices = []openai.ChatCompletionStreamChoice{{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{Content: event.Delta},
}}
return response, nil
case openai.ResponseStreamEventRefusalDelta:
stream.responseContent.WriteString(event.Delta)
response.Choices = []openai.ChatCompletionStreamChoice{{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{Content: event.Delta, Refusal: event.Delta},
}}View on GitHub (pinned to afa823b6b4)
Solutions
- Retry the request — transient connection cuts are the leading cause.
- If reproducible, fix the transport: disable SSE buffering and raise read/idle timeouts on every proxy and gateway in the path.
- Test the same request against the official OpenAI endpoint to see whether the provider itself omits the terminal event; upgrade it if so.
- Fall back to non-streaming CreateOpenAICompletion for requests that keep failing.
Example fix
// before: single attempt, fail hard
stream, err := util.CreateOpenAICompletionStream(ctx, client, protocol, req, input)
// after: bounded retry around the whole streamed request
for attempt := 0; attempt < 3; attempt++ {
stream, err = util.CreateOpenAICompletionStream(ctx, client, protocol, req, input)
if err == nil {
if err = consume(stream); err != nil && strings.Contains(err.Error(), "terminal event") && attempt < 2 {
continue // premature EOF, retry
}
}
break
} Defensive patterns
Strategy: retry
Try / catch
resp, err := stream.Recv()
if err != nil {
if strings.Contains(err.Error(), "ended before a terminal event") {
stream.Close()
// retry the whole streamed request with backoff, or fall back to:
return util.CreateOpenAICompletion(ctx, client, protocol, req, input) // non-streaming
}
return nil, err
} Prevention
- Keep SSE connections free of buffering proxies
- Set generous read timeouts for long generations
- Monitor premature-termination rates per provider
- Keep a non-streaming fallback path configured
When it happens
Trigger: Streaming a Responses-protocol completion (protocol "openai-responses") when the connection drops or a proxy closes the SSE channel early; gateway idle/read timeouts cutting long generations; providers that end the event stream without ever emitting response.completed; flaky mobile or VPN networks.
Common situations: Reverse proxies (nginx, Cloudflare) buffering or timing out SSE; self-hosted gateways with short idle timeouts; very long tool-call generations exceeding intermediate hop limits; unstable networks on mobile clients.
Related errors
- response stream failed
- response stream terminal event is missing response
- response failed
- AI editor stream idle timeout
- streamed function call is missing from the terminal response
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/a76a2414bd7694a3.
Report an issue: GitHub.