Skip to content

[Bug] GLM-5.1 streaming: truncated JSON in SSE chunks causes client-side parse failures #66

Closed

Description

@darkhipo

Summary

GLM-5.1 (via z.ai API and vLLM deployments) intermittently produces truncated JSON in SSE streaming chunks, causing JSON Parse error: Expected '}' on the client side. This breaks streaming conversations in tools like opencode, killing the entire session.

Observed Behavior

During streaming inference, SSE chunks arrive with JSON objects that are truncated mid-field — the closing portion of the JSON object is missing, and the next SSE data: frame begins inline within the previous frame's content:

JSON parsing failed: Text: {"id":"20260430204055297b97348c8e426c","created":1777552855,"object":"chat.cdata: {"id":"2026043020420635c6abc56a2741ac",
"created":1777552926,"object":"chat.completion.chunk","model":"glm-5.1","choices":[{"index":0,"delta":{"role":"assistant","content":"11"}}]}
Error message: JSON Parse error: Expected '}'

Notice the first object ends at "object":"chat.c — this is the beginning of "chat.completion.chunk" that got cut off. Then the second SSE data: frame starts immediately, producing invalid JSON.

Two Distinct Failure Modes

Mode 1: Truncated SSE chunks (this report)

The SSE frame contains incomplete JSON — the server sends a chunk that is cut off before the closing }. The next chunk's data: prefix appears within the previous chunk's text.

Frequency: Intermittent, occurs during long multi-turn sessions with 50+ tool calls. Not every session, but frequent enough to be a reliability blocker.

Mode 2: Hallucinated SSE content in model output

Related but distinct: the model sometimes generates data: {"id":"...","choices":[...]} as literal text in its response content field. Z.AI's server doesn't escape the inner quotes, breaking the outer JSON structure.

Tracked in: anomalyco/opencode#23442

Environment

Component Version
Model zai-coding-plan/glm-5.1
Provider Z.AI API (api.z.ai/api/coding/paas/v4)
Client opencode 1.4.10 (darwin-arm64, homebrew)
Hardware Apple M1 Air 8GB
Protocol OpenAI-compatible SSE streaming
Session context 50+ tool calls, ~100k tokens context

Also reproduced by others via:

Root Cause Analysis

The truncation pattern ("object":"chat.c followed immediately by data: {) suggests one of:

  1. Server-side SSE framing bug: The z.ai API server (or vLLM with glm47 parser) generates SSE data: frames that contain incomplete JSON objects — perhaps due to a buffer flush boundary that splits mid-JSON.

  2. Streaming serialization race: The JSON serializer writes partial output and flushes before the object is complete, producing a truncated frame.

  3. Token-level chunking mismatch: The model generates tokens that cross a JSON structural boundary, and the server doesn't buffer until the boundary is complete before emitting the SSE frame.

The fact that this reproduces across multiple deployment backends (z.ai hosted, vLLM, NVIDIA NIM) suggests it may be model-level — the tokenizer or generation logic produces output that confuses the server-side SSE formatter.

Reproduction

  1. Configure any OpenAI-compatible client with GLM-5.1
  2. Engage in a multi-turn conversation with 50+ tool calls
  3. Observe intermittent JSON parse errors in SSE chunks
  4. The client-side stream dies, killing the conversation

Minimal curl reproduction (will not trigger every time due to intermittency):

curl -N https://api.z.ai/api/coding/paas/v4/chat/completions \
  -H "Authorization: Bearer $ZAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "glm-5.1",
    "stream": true,
    "messages": [{"role": "user", "content": "Write a complex Python class with 20 methods"}]
  }' 2>&1 | grep -c '}'
# Count should equal number of data: lines; mismatches indicate truncation

Expected Behavior

Every SSE data: frame should contain a complete, parseable JSON object. The server should buffer partial JSON until a complete object is ready before emitting the frame.

Actual Behavior

SSE frames contain truncated JSON objects, with the next frame's prefix appearing inline.

Impact

  • Session death: The entire conversation dies when a malformed chunk hits the JSON parser
  • No recovery: Current clients (opencode, Vercel AI SDK) treat parse errors as fatal stream errors
  • Reliability blocker: Cannot use GLM-5.1 for long autonomous sessions where reliability matters

Suggested Server-Side Fix

Buffer the JSON output token-by-token and only emit an SSE data: frame when the JSON object is complete (balanced braces). This ensures every frame is parseable:

# Pseudocode for SSE emission
buffer = ""
brace_depth = 0
for token in model_stream():
    buffer += token
    brace_depth += token.count('{') - token.count('}')
    if brace_depth == 0 and buffer.strip():
        yield f"data: {buffer}\n\n"
        buffer = ""

Suggested Client-Side Mitigation (for opencode/others)

Skip malformed chunks and continue the stream instead of dying:

// In parseJsonEventStream TransformStream:
try {
  controller.enqueue(await safeParseJSON({ text: data, schema }));
} catch (err) {
  console.warn("SSE chunk JSON parse failed, skipping:", data.slice(0, 200));
  // Continue to next chunk — real content is in adjacent valid frames
}

Related Issues

Why This Matters

We run GLM-5.1 as a coding agent inside the MultiAgency framework — an autonomous system where reliability is critical. A single truncated SSE chunk kills an entire session that may have hours of accumulated context. We want to use GLM-5.1 as a first-class model in our rotation, but this issue makes it unreliable for long autonomous sessions.

We're happy to help test fixes or provide more detailed logs from our sessions.

Activity

  1. JaredforReal commented on May 6, 2026

    @JaredforReal
    Contributor

    @darkhipo Thanks for your feedback! But I didn't success to repro the issue. Here is the script I used test_sse_issue.py
    Correct me if I did not get things right, and glad to have more information from ur side
    BTW, vllm-project/vllm#39253, we have fixed a issue related to streaming tool call parser in vllm like a month ago, just for your information

  2. wong-hao commented on May 7, 2026

    @wong-hao

    @darkhipo Thanks for your feedback! But I didn't success to repro the issue. Here is the script I used test_sse_issue.py Correct me if I did not get things right, and glad to have more information from ur side BTW, vllm-project/vllm#39253, we have fixed a issue related to streaming tool call parser in vllm like a month ago, just for your information

    I have the same problem with GLM5.1 when tool calling. NousResearch/hermes-agent#13042

  3. darkhipo commented on May 9, 2026

    @darkhipo
    Author

    Thanks for the test script, @JaredforReal — it's well-structured. A few notes on why it likely didn't reproduce:

    The bug requires sustained multi-turn tool-calling sessions. Your script does single-turn completions in a loop, but each request starts fresh. The truncation we see only triggers after 50+ accumulated tool calls in a single streaming session where context exceeds ~100k tokens. The pattern is:

    1. Session starts fine
    2. After 50+ tool-call rounds with accumulating context, SSE frames begin arriving truncated
    3. The truncation looks like: "object":"chat.c followed immediately by data: { — i.e., the server splits a JSON object mid-field across two SSE frames

    Your test script sends individual requests — it won't hit the accumulated-context degradation path. To reproduce, you'd need a single persistent connection with the full conversation history growing across turns (like a coding agent loop does).

    However, the vllm PR you linked (vllm-project/vllm#39253) may have fixed the server-side root cause for self-hosted deployments. That PR rewrote the GLM tool parser from a buffer-based state machine to a stateless re-parse approach — exactly the kind of fix that would prevent mid-JSON frame splits. We'll test against a recent vllm build to confirm.

    For the z.ai hosted API specifically, the issue still reproduces as of this week. Since we can't control the server-side build there, the question is whether z.ai's API backend includes the fix from that vllm PR.

    @wong-hao — thanks for confirming. Your hermes-agent issue (NousResearch/hermes-agent#13042) describes the same degradation pattern: "works fine for short-context tool calls, malforms in long contexts." This matches our experience exactly.

    We've added client-side mitigation on our end (skip malformed chunks, continue the stream), which reduces session deaths but doesn't fix the root cause. The real fix needs to be server-side — either the z.ai API backend picking up the vllm parser fix, or ensuring the SSE framing layer buffers complete JSON objects before emitting frames.

  4. darkhipo commented on May 9, 2026

    @darkhipo
    Author

    New Reproduction Data — 2026-05-10

    Hit the same truncation again today during an active coding session. This time the truncation point is slightly different — cutting the id field instead of the object field.

    Raw Error

    JSON parsing failed: Text: {"id":"2026051004data: {"id":"202605100452505383be940b9c458d","created":1778359970,"object":"chat.completion.chunk","model":"glm-5.1","choices":[{"index":0,"delta":{"role":"assistant","content":"Now"}}]}
    Error message: JSON Parse error: Expected '}'
    

    Breakdown

    The first SSE frame is truncated at the id field:

    {"id":"2026051004

    Then data: appears inline, followed by a complete second frame:

    {"id":"202605100452505383be940b9c458d","created":1778359970,"object":"chat.completion.chunk","model":"glm-5.1","choices":[{"index":0,"delta":{"role":"assistant","content":"Now"}}]}

    Pattern Analysis

    Comparing across our three captured incidents:

    Incident Truncation point Truncated field value
    2026-04-30 "object":"chat.c Start of "chat.completion.chunk"
    2026-05-10 (a) "id":"2026051004 Timestamp-based ID, cut at char 12
    2026-05-10 (b) (second frame was complete) —

    The truncated id value "2026051004" is exactly the first 12 characters of the second frame's complete id "202605100452505383be940b9c458d". This is consistent with a fixed buffer size (possibly 12 bytes?) that flushes mid-field when the JSON object exceeds the buffer boundary.

    Session Context

    • Model: zai-coding-plan/glm-5.1
    • Provider: Z.AI hosted API
    • Session state: Mid-coding task, reviewing a workplan file, ~40 tool calls into the session
    • Context size: ~60-80k tokens (moderate — well under the 100k threshold from earlier incidents)
    • The truncation happened during a normal text response, NOT during tool calling. The model was producing the word "Now" as its next content token.

    Key Observation: Not Limited to Long Contexts

    This incident occurred at ~60-80k tokens context, which is lower than our previous incidents at 100k+. This suggests the truncation is not purely a function of context length — it may be related to response generation timing, server load, or buffer management that can trigger at any session length.

    Suggested Server-Side Debugging

    The consistent truncation at fixed character boundaries (12 chars of the id field, 8 chars of "chat.c) suggests a buffer flush threshold. If the server uses a fixed-size output buffer for SSE framing, look for:

    1. A buffer that flushes on size rather than on JSON completeness
    2. A race condition between the token stream and the SSE writer
    3. An id field that's generated from a timestamp and may have variable length (longer IDs = more likely to cross the buffer boundary)

    If you can share any details about the SSE emission layer in the z.ai API server, we can provide more targeted analysis.

  5. Diego404YT commented on May 16, 2026

    @Diego404YT

    Confirming this on the chat.z.ai web frontend

    I am experiencing the exact same truncation behavior when using the web interface at chat.z.ai.

    Symptoms:

    Stream suddenly cuts off mid-response.
    Or the entire response "pops" in all at once instead of streaming token-by-token.
    The thinking process is often interrupted or only displayed at the very end.
    I ran a network debugger on the frontend and captured the SSE chunks. The backend streams the thinking phase correctly at first, but then a malformed/truncated chunk kills the stream parsing on the client side, causing the UI to crash the generation or dump the buffer all at once.

    This confirms the bug isn't just limited to OpenCode/vLLM; it's breaking the official hosted web chat as well. Any server-side fix for the SSE framing will fix the web UI too.

  6. self-assigned this
    on May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions