﻿# SSE completion: blank lines, DONE and unexpected EOF

Distinguish SSE event completion, DONE and HTTP closure using minimal traces, unexpected EOF cases, usage checks and explicit completeness states.

Author: Retrivium team
Updated: 2026-11-01
Canonical URL: https://retrivium.ai/journal/provider-sse-terminal-eof-contract/
Language: en

The last message read is not necessarily the generation's final message. A client may receive some content and encounter EOF after a network failure. Protocol tests must separate SSE event completion, application termination and transport closure. An incomplete result then cannot become successful merely because its final line looks like valid JSON.

## Name three distinct boundaries

The [SSE standard](https://html.spec.whatwg.org/multipage/server-sent-events.html) defines event extraction from text. A blank line participates in dispatching an event but is not a universal end-of-generation signal. An application protocol may add its own terminal marker, which the format parser does not interpret by itself.

Store three observations: whether the last event is complete, whether the expected application outcome arrived and whether body reading ended. Their combinations diagnose more precisely than one success flag. Never automatically reconstruct a missing observation from another's presence.

## Establish the normal trace

The [OpenRouter streaming documentation](https://openrouter.ai/docs/api_reference/streaming) describes final usage before DONE for Chat Completions. Pin the endpoint and current shape. Other interfaces may use their own terminal events, so DONE must not become a universal inference API requirement.

A minimal fixture contains content events, a valid terminal outcome and closure. Save expected event count and assembled text. This control trace anchors deliberately incomplete variants. A real model is unnecessary: a small fake upstream with known bytes is enough.

## Truncate at different positions

One variant ends inside a data line, another after valid JSON but before its event separator, and another after text but before accounting or the terminal marker. Expected outcomes differ. Do not append separators and declare success merely to simplify parsing.

Preserve the cut position and last fully processed event. An unfinished parser buffer's size helps diagnosis, while real request contents need not enter production logs by default. A synthetic fixture can retain its complete input without that uncertainty.

## Separate HTTP framing from SSE

[RFC 9112](https://www.rfc-editor.org/rfc/rfc9112.html) describes HTTP/1.1 message completeness and framing. A correctly ended HTTP body does not establish that application generation completed under the expected contract. Conversely, an early application marker does not excuse failing to handle remaining defined accounting data.

The fault may occur at any layer: transport reports an incomplete message, SSE lacks a finished event, or the application misses its promised outcome. Record the first observed failing layer while retaining other observations. One network fault should not become several supposedly independent model errors.

## Classify results cautiously

Completed, incomplete and explicitly failed outcomes are useful distinctions. Partial text can support diagnosis but should not enter complete-answer counters without an explicit policy. Automatic retries also need care because upstream work may already have happened.

Related guides cover [stream errors](/journal/openrouter-stream-errors/) and [incremental clients](/journal/openrouter-streaming-client/). This test establishes delivery completeness for the selected protocol, not prose quality, payment status or universal provider guarantees.
