1jehuang / 1jehuang/jcode

[Bug] SSE stream data corruption: tool call arguments truncated, Chinese text lost, JSON concatenation errors

Open
#610 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug duplicate priority: high
Dominant language
Rust
Stars
19.9k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
30

Description

Description

When using OpenAI-compatible providers via the OpenRouter SSE stream, three forms of data corruption occur:

1. JSON concatenation

The proxy/upstream removes the \n\n event separator between two SSE data: lines, producing concatenated JSON like {"id":"a"}{"id":"b"...} or {"id":"a"}data: {"id":"b"...}. serde_json::from_str rejects this with Extra data and drops the entire chunk.

2. Cross-event JSON truncation

The proxy splits a JSON object across two SSE events:

  • Event 1: data: {"choices":[{"delta":{"content":"Hello "
  • Event 2: data: world"}}]}
    The first event's brace depth never reaches zero, so the chunk is discarded.
3. UTF-8 boundary corruption

When a multi-byte UTF-8 character (e.g. Chinese = 3 bytes) is split across TCP chunks, std::str::from_utf8() fails on the individual chunk, and the entire chunk is silently dropped. This manifests as missing Chinese characters or \u{FFFD} replacement characters.

Symptoms

  • Tool calls fail with Invalid tool call: arguments must be a JSON object, got null
  • Tool call arguments truncated mid-string
  • Chinese text in table columns missing or corrupted
  • Data written via write tool has missing content
  • Frequent OpenRouter SSE JSON parse failed warnings in logs

Root Causes

  1. String buffer with from_utf8 per chunk: Using String as SSE buffer requires calling from_utf8 on each incoming TCP chunk. When a multi-byte character is split across chunks, from_utf8 fails and the entire chunk is lost.

  2. serde_json::from_str rejects concatenated JSON: Multiple JSON values in one string cause Extra data error; the entire blob is discarded instead of extracting individual values.

  3. No partial JSON reassembly: When a JSON object is truncated mid-stream (brace never closes), there is no mechanism to save the partial text and combine it with the next event.

Impact

  • Data loss: Tool call arguments are silently truncated
  • Failed execution: arguments=null causes tool calls to be rejected
  • Display corruption: Table columns vanish, Chinese characters replaced with
  • Frequency: Hundreds of parse errors per session with affected providers

Proposed Fix

The fix addresses all three root causes:

a) Raw byte buffer instead of String

Change the SSE buffer from String to Vec<u8>. Accumulate raw bytes and only decode to String after a complete SSE event boundary (\n\n or \r\n\r\n) is found, ensuring UTF-8 sequences are always complete.

b) Brace-depth JSON splitting

Replace serde_json::from_str with a brace-depth matcher that scans {/} nesting. This correctly handles both concatenated JSONs ({"a":1}{"b":2}) and embedded data: prefix ({"a":1}data: {"b":2}).

c) Partial JSON reassembly

When brace-depth never reaches zero (cross-event truncation), save the incomplete JSON text and prepend it to the next event's data before parsing.

d) Stream-end buffer flush

When the SSE stream closes without a trailing \n\n, force-close the buffer and process remaining data.

Log evidence (before vs after fix)

Metric Before (8h session) After (12h session)
SSE JSON parse failures 719 4 (edge cases)
Invalid tool calls 74 1

Environment

  • jcode version: v0.58.x
  • Provider: OpenAI-compatible (DeepSeek, OpenCode Go, etc.)
  • Transport: SSE over HTTPS
  • Models: deepseek-v4-flash, glm-5

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Locate the SSE buffering and JSON parsing entry point, then reproduce the listed concatenated-JSON, cross-event truncation, UTF-8 boundary, and stream-end cases. Done means complete SSE events preserve multibyte text, concatenated and partial JSON values are processed, trailing data is flushed, and the reported parse failures and invalid tool calls are reduced.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.