Skip to content

Conversation

@c4milo
Copy link

@c4milo c4milo commented Oct 31, 2025

… errors

Fix crash when parsing SSE streams that contain empty events from retry: directives or comment lines. Fixes #556

Problem

The eventStreamDecoder creates events with empty Data when it encounters empty lines after non-data SSE fields (like "retry: 3000"). Stream.Next() then attempts json.Unmarshal on empty bytes, causing "unexpected end of JSON input" error. This breaks streaming with any SSE server using the retry directive.

Root Cause

Per the SSE specification [1], events are dispatched when empty lines are encountered, regardless of whether data was present.

The spec states for empty line handling:

"If the line is empty (a blank line) [Dispatch the event], as defined below."

And for the retry field:

"If the field value consists of only ASCII digits, then interpret the field
value as an integer in base ten, and set the event stream's reconnection time
to that integer. Otherwise, ignore the field."

For empty data handling:

"If the data buffer is an empty string, set the data buffer and the event
type buffer to the empty string and return."

This means that a sequence like:

retry: 3000

Creates a valid empty event according to the spec. Servers commonly send this for reconnection configuration, but the SDK assumed all events contain JSON data.

[1] https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events

Solution

Check if event.Data is empty before attempting to unmarshal. Skip empty events and continue processing the stream. This maintains compatibility with OpenAI API while supporting standard SSE practices per spec.

Tests Added

  • TestStream_EmptyEvents: Verifies handling of retry directive with empty event
  • TestStream_OnlyRetryDirective: Tests stream with only retry (no data)
  • TestStream_MultipleEmptyEvents: Tests multiple empty events interspersed with data

All tests pass:

=== RUN   TestStream_EmptyEvents
--- PASS: TestStream_EmptyEvents (0.00s)
=== RUN   TestStream_OnlyRetryDirective
--- PASS: TestStream_OnlyRetryDirective (0.00s)
=== RUN   TestStream_MultipleEmptyEvents
--- PASS: TestStream_MultipleEmptyEvents (0.00s)
PASS

Impact

  • Enables compatibility with SSE servers using retry: directive (common practice)
  • No breaking changes - only adds resilience to spec-compliant edge case
  • Verified with streaming function calling through Anthropic API gateway

Real-World Testing

Tested with Anthropic Claude 3.5 streaming API via AI Gateway:

  • Before: "Stream error: unexpected end of JSON input"
  • After: Successfully receives and processes all streaming chunks

Fixes stream crashes with "unexpected end of JSON input" when encountering SSE streams with retry directives or comment lines.

… errors

Fix crash when parsing SSE streams that contain empty events from retry:
directives or comment lines.

## Problem

The eventStreamDecoder creates events with empty Data when it encounters
empty lines after non-data SSE fields (like "retry: 3000"). Stream.Next()
then attempts json.Unmarshal on empty bytes, causing "unexpected end of
JSON input" error. This breaks streaming with any SSE server using the
retry directive.

## Root Cause

Per the SSE specification [1], events are dispatched when empty lines are
encountered, regardless of whether data was present.

The spec states for empty line handling:
> "If the line is empty (a blank line) [Dispatch the event], as defined below."

And for the retry field:
> "If the field value consists of only ASCII digits, then interpret the field
> value as an integer in base ten, and set the event stream's reconnection time
> to that integer. Otherwise, ignore the field."

For empty data handling:
> "If the data buffer is an empty string, set the data buffer and the event
> type buffer to the empty string and return."

This means that a sequence like:
```
retry: 3000

```
Creates a valid empty event according to the spec. Servers commonly send this
for reconnection configuration, but the SDK assumed all events contain JSON data.

[1] https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events

## Solution

Check if event.Data is empty before attempting to unmarshal. Skip empty
events and continue processing the stream. This maintains compatibility
with OpenAI API while supporting standard SSE practices per spec.

## Tests Added

- TestStream_EmptyEvents: Verifies handling of retry directive with empty event
- TestStream_OnlyRetryDirective: Tests stream with only retry (no data)
- TestStream_MultipleEmptyEvents: Tests multiple empty events interspersed with data

All tests pass:
```
=== RUN   TestStream_EmptyEvents
--- PASS: TestStream_EmptyEvents (0.00s)
=== RUN   TestStream_OnlyRetryDirective
--- PASS: TestStream_OnlyRetryDirective (0.00s)
=== RUN   TestStream_MultipleEmptyEvents
--- PASS: TestStream_MultipleEmptyEvents (0.00s)
PASS
```

## Impact

- Enables compatibility with SSE servers using retry: directive (common practice)
- No breaking changes - only adds resilience to spec-compliant edge case
- Verified with streaming function calling through Anthropic API gateway

## Real-World Testing

Tested with Anthropic Claude 3.5 streaming API via AI Gateway:
- Before: "Stream error: unexpected end of JSON input"
- After: Successfully receives and processes all streaming chunks

Fixes stream crashes with "unexpected end of JSON input" when encountering
SSE streams with retry directives or comment lines.
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.

Bug: SSE Stream Crashes on Empty Events (retry directives, comment lines)

1 participant