Skip to content

test(ssestream): discard incomplete events at EOF#697

Open
fallintoplace wants to merge 1 commit into
openai:mainfrom
fallintoplace:fix/ssestream-final-event-eof
Open

test(ssestream): discard incomplete events at EOF#697
fallintoplace wants to merge 1 commit into
openai:mainfrom
fallintoplace:fix/ssestream-final-event-eof

Conversation

@fallintoplace

@fallintoplace fallintoplace commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Why

EOF is not an SSE event delimiter. When a stream ends without a terminating blank line, the pending event must be discarded. This PR adds regression coverage for that behavior, including type-only blocks that must not produce empty data.

What changed

  • Add decoder coverage for data and type-only blocks that end at EOF without a blank line.
  • Add typed-stream coverage confirming those incomplete blocks are discarded without an error.
  • Preserve normal dispatch for properly delimited SSE events.

This PR adds test coverage only; the runtime behavior is already standards-compliant.

Validation

  • go test -count=1 -race ./packages/ssestream

@fallintoplace
fallintoplace requested a review from a team as a code owner June 27, 2026 09:59

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the focused change and the clear regression tests — I appreciate you taking the time to investigate this edge case.

I found two blocking issues in the proposed EOF dispatch path:

  1. The behavior conflicts with the normative SSE parsing algorithm. The WHATWG HTML Standard says that once EOF is reached, pending data is discarded and an incomplete event is not dispatched; it also gives an explicit example noting that the last event still needs a blank line. See https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation.
  2. The event != "" condition emits type-only blocks with empty data. I reproduced this on the PR head with event: update at EOF: the decoder returned {Type:update Data:[]}, and Stream.Next() then failed with unexpected end of JSON input. That is the same empty-data failure mode recently fixed by #621.

The existing go test ./packages/ssestream suite passes on the branch, but focused decoder and typed-stream regressions for the type-only EOF case both fail. I’ve left inline comments with the details.

Comment thread packages/ssestream/ssestream.go Outdated
return false
}

if data.Len() > 0 || event != "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This EOF dispatch is contrary to the SSE parsing algorithm: the WHATWG specification says pending data must be discarded at EOF and that an incomplete event is not dispatched. Separately, || event != "" reintroduces an empty-data failure: with event: update followed by EOF, this returns {Type: "update", Data: []}, and a typed Stream terminates with unexpected end of JSON input. Please keep EOF from dispatching incomplete blocks; if non-standard tolerance is intentionally desired, it needs an explicit compatibility decision and must at least never dispatch when data.Len() == 0.

Comment thread packages/ssestream/ssestream_test.go Outdated
Header: http.Header{
"Content-Type": []string{"text/event-stream"},
},
Body: io.NopCloser(strings.NewReader("event: update\ndata: hello")),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixture encodes behavior that the SSE standard explicitly rejects: EOF is not an event delimiter, and the pending event:/data: block must be discarded without a final blank line. The compliant fixture would end with \n\n; without that delimiter, decoder.Next() should return false.

@fallintoplace
fallintoplace force-pushed the fix/ssestream-final-event-eof branch from d7a4dea to c0410cb Compare July 10, 2026 08:20
@fallintoplace
fallintoplace force-pushed the fix/ssestream-final-event-eof branch from c0410cb to f0ec517 Compare July 10, 2026 08:22
@fallintoplace

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. I’ve removed the EOF dispatch behavior and updated the tests to assert the standards-compliant behavior: buffered events without a terminating blank line are discarded at EOF, including type-only events with no data.

@fallintoplace fallintoplace changed the title Emit the final SSE event when a stream ends at EOF Discard incomplete SSE events at EOF Jul 10, 2026
@fallintoplace fallintoplace changed the title Discard incomplete SSE events at EOF test(ssestream): discard incomplete events at EOF Jul 10, 2026

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the feedback and for the focused regression coverage. I re-reviewed the updated change and everything looks good now. Approved!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants