Skip to content

fix(streaming): skip SSE events with empty data to prevent JSON parse crash#903

Open
DukeDeSouth wants to merge 1 commit intoanthropics:mainfrom
DukeDeSouth:fix/streaming-empty-sse-events
Open

fix(streaming): skip SSE events with empty data to prevent JSON parse crash#903
DukeDeSouth wants to merge 1 commit intoanthropics:mainfrom
DukeDeSouth:fix/streaming-empty-sse-events

Conversation

@DukeDeSouth
Copy link

@DukeDeSouth DukeDeSouth commented Feb 6, 2026

Human View

Problem

In edge and cloud environments (Vercel Edge, Cloudflare Workers, AWS Lambda@Edge), network proxies and CDNs can split SSE chunks so that an event line (event: content_block_delta) arrives without its corresponding data: line. The SSEDecoder correctly produces a ServerSentEvent with data: '', but Stream.fromSSEResponse crashes with:

SyntaxError: Unexpected end of JSON input
    at (src/core/streaming.ts)

when it calls JSON.parse('') on the empty data.

This is a regression of #292 (originally fixed in v0.17.0 via #312 for LineDecoder). The root cause is different: the LineDecoder fix ensured correct line splitting, but Stream.fromSSEResponse never had a guard against empty data in the event handlers.

Impact

  • Streams crash and abort in edge/cloud environments
  • Users see "Unexpected end of JSON input" errors
  • Workaround requires @ts-ignore or custom patching
  • Reported across Vercel Edge, Cloudflare Workers, and various cloud Node.js deployments

Root Cause

SSEDecoder.decode() accumulates data from data: lines and joins them with \n. When an event has no data: line, this.data is [], and [].join('\n') produces '' (empty string). Stream.fromSSEResponse then blindly calls JSON.parse('') for known event types (completion, message_start, content_block_delta, etc.), which throws.

Why PR #861 doesn't fix this

The existing PR #861 checks sse.data == null, but SSEDecoder always sets data to a string value. For events without data, data is '' (empty string), and '' == null evaluates to false in JavaScript. The check never triggers.

Fix

Added an empty-data guard (if (!sse.data) continue) before JSON.parse() for all event types that expect JSON data:

  • completion
  • message_start, message_delta, message_stop
  • content_block_start, content_block_delta, content_block_stop

The falsy check !sse.data correctly catches both empty strings ('') and theoretically null/undefined values. Events with empty data are silently skipped, allowing the stream to continue processing subsequent valid events.

Tests Added (16 new tests)

Test Description
Per-event-type (7 tests) Each Anthropic event type with empty data is skipped without crashing
Stream continuation Valid events are processed after empty-data events
Interleaved events Mix of valid and empty-data events produces correct output
Realistic edge simulation Full message lifecycle with empty events from network splits
Ping handling Ping events with empty data still work correctly
Error events (2 tests) Error events with/without data still throw APIError
Abort controller Abort works correctly when empty events are present
All-empty stream Stream with only empty events completes without error
Stress test 100 events with every 3rd having empty data — all handled correctly

All 26 streaming tests pass (10 pre-existing + 16 new).

Checklist

  • Fix applied to both completion and message event handlers
  • Correctly uses falsy check (!sse.data) instead of null check (== null)
  • Does not modify SSEDecoder behavior (SSE spec compliance preserved)
  • Error events continue to throw APIError as expected
  • All pre-existing tests still pass
  • 16 new tests cover all event types, edge cases, and stress scenarios

Fixes #292 (regression). Supersedes #861.


AI View (DCCE Protocol v1.0)

Metadata

  • Generator: Claude (Anthropic) via Cursor IDE
  • Methodology: AI-assisted development with human oversight and review

AI Contribution Summary

  • Root cause analysis through code tracing
  • Solution design and implementation
  • Test development (16 new test cases)
  • Edge case analysis and verification

Verification Steps Performed

  1. Reproduced the reported issue
  2. Analyzed source code to identify root cause
  3. Implemented and tested the fix

Human Review Guidance

  • Verify the root cause analysis matches your understanding of the codebase
  • Core changes are in: this.data
  • Verify edge case coverage is complete

Made with M7 Cursor

… crash

In edge and cloud environments (Vercel Edge, Cloudflare Workers), network
proxies and CDNs can split SSE chunks so that an event line arrives without
its corresponding data line. The SSEDecoder correctly produces an event with
data='', but Stream.fromSSEResponse crashes with "Unexpected end of JSON
input" when trying to JSON.parse('').

This adds an empty-data guard for all event types that expect JSON data:
- completion
- message_start, message_delta, message_stop
- content_block_start, content_block_delta, content_block_stop

Events with empty data are silently skipped, allowing the stream to
continue processing subsequent valid events.

Note: The existing PR anthropics#861 checks `sse.data == null`, which does not
catch this bug since SSEDecoder always sets data to a string (empty
string '' for events without a data line, and '' == null is false).

Fixes anthropics#292 (regression), addresses anthropics#861

Added 16 new tests covering:
- Per-event-type empty data handling (7 event types)
- Stream continuation after empty events
- Interleaved empty and valid events
- Realistic edge-environment simulation
- Error event behavior with empty data
- Abort controller interaction
- All-empty stream completion
- Stress test (100 events with intermittent empty data)

Co-authored-by: Cursor <cursoragent@cursor.com>
@DukeDeSouth DukeDeSouth requested a review from a team as a code owner February 6, 2026 23:59
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.

"Unexpected end of JSON input" when streaming on edge environments (Vercel Edge, Cloudflare Workers)

1 participant