Repository navigation
OpenAI Chat SSE parser ignores valid data:{...} frames without a space #1170
Description
Client or integration
Other — downstream application embedding the OpenCodex proxy.
Provider or upstream service
Private OpenAI-compatible model gateway (provider identity and endpoint redacted).
OpenCodex version
@bitkyc08/opencodex 2.7.42
Endpoint or capability
/v1/chat/completions streaming (SSE)
Current behaviour
The OpenAI Chat adapter only processes lines starting with data: (a colon followed by a space).
The upstream emits valid SSE data fields without that optional space, for example data:{...}. As a result, every JSON frame is ignored. A terminal chunk carrying finish_reason: "stop" and a trailing usage-only chunk are also ignored. When the upstream closes the stream, OpenCodex reports:
upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation
Expected behaviour
OpenCodex should accept both data:{...} and data: {...} as SSE data fields.
A non-empty OpenAI finish_reason should remain a valid terminal signal even when the upstream omits the OpenAI-specific data: [DONE] sentinel.
Minimal redacted request or reproduction
curl -N -sS "$BASE_URL/chat/completions" \
-H "Content-Type: application/json" \
-H "X-Example-Auth: <redacted>" \
-d '{
"model": "<model>",
"messages": [{"role": "user", "content": "hello"}],
"stream": true
}'The upstream response uses this framing pattern:
data:{"id":"<id>","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"..."},"finish_reason":null}]}
...
data:{"id":"<id>","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data:{"id":"<id>","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
There is no data: [DONE] frame.
Actual response or error
MODEL_RUNTIME_CANARY_FAILED: turn status failed; stream disconnected before completion:
upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation
Upstream documentation
SSE field syntax permits the value to begin immediately after the colon; one optional U+0020 space is stripped when present:
https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
The non-null finish_reason behavior is an OpenAI-compatible streaming convention. The upstream has no public provider-specific specification.
Suggested mapping or implementation notes
The shared data-line handler can accept the data: field name regardless of whether a space follows it:
if (!line.startsWith("data:")) return "continue";
const payload = line.slice("data:".length).trim();Regression coverage should include:
- Unspaced
data:{...}frames with a final non-nullfinish_reasonand no[DONE]-> successful completion. - Standard
data: {...}framing -> unchanged behavior. - A genuinely truncated stream with no
[DONE], no finalfinish_reason, and no terminal usage frame -> still fail closed.
Additional context and attachments
The report is fully redacted. No production endpoint, model identifier, request content, or credentials are included.
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
Activity
github-actions commentedon Aug 7, 2026
Automated translation bookkeeping — detected language: unknown.
github-actions commentedon Aug 7, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
test(openai-chat): isolate the unspaced [DONE] sentinel path (lidge-j…
fix(openai-chat): accept SSE data fields without the optional space (l…
test(openai-chat): isolate the unspaced [DONE] sentinel path (lidge-j…
fix(openai-chat): accept SSE data fields without the optional space (l…
test(openai-chat): isolate the unspaced [DONE] sentinel path (lidge-j…
fix(sse): accept unspaced `data:` fields across six parsers (#1170)
test(claude): activate the budgeted raw-frame parser with unspaced fi…
fix(sse): accept unspaced `data:` fields across six parsers (#1170)
test(claude): activate the budgeted raw-frame parser with unspaced fi…
test(openai-chat): prove the terminal finish_reason frame was parsed (l…
fix(openai-chat): accept SSE data fields without the optional space (l…
test(openai-chat): isolate the unspaced [DONE] sentinel path (lidge-j…
test(openai-chat): prove the terminal finish_reason frame was parsed (l…
Wibias commentedon Aug 7, 2026
Closing as completed by #1194.
The affected SSE parsers now accept valid unspaced data: fields such as data:{...} while preserving terminal-signal and truncation behaviour.
fix(sse): accept unspaced `data:` fields across six parsers (lidge-ju…
test(claude): activate the budgeted raw-frame parser with unspaced fi…