OpenAI Responses: EOF without a terminal event is reported as a successful stop
Maintainer thường phản hồi trong vòng 1 ngày
@tombeckenham đang làm issue này rồi.
Từ ngày 22/9/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
TanStack AI version
@tanstack/ai:0.58.0@tanstack/ai-openai:0.23.1@tanstack/openai-base:0.10.15- Underlying
openaiSDK:6.41.0
Also reproduced with the underlying openai SDK pinned to 6.49.0, with both @tanstack/ai-openai and @tanstack/openai-base verified to resolve that version. The shared reproduction keeps its original 6.41.0 lockfile.
Framework/Library version
No UI framework. Reproduced with Node.js 24.14.1 and Bun 1.3.12 on Linux.
Describe the bug and the steps to reproduce it
When an OpenAI Responses stream reaches EOF after text deltas but before any protocol terminal event, chat() reports a successful completion: it emits RUN_FINISHED with metadata.tanstack.finishReason: "stop" and invokes the middleware's onFinish. No RUN_ERROR or onError is produced, although the client has received no confirmation that generation finished.
This conflicts with the documented success semantics: RunFinishedEvent is described as "Emitted when a run completes successfully." The onFinish contract says it is called when the chat run "completes normally," and that exactly one of onFinish / onAbort / onError is called per run.
For example, an incomplete answer containing only "The answer is " receives the same successful lifecycle outcome as the completed answer "The answer is 42.". Callers using the terminal event or middleware callback to decide whether generation succeeded can consequently accept partial output as complete.
Steps to reproduce
The reproduction uses the public chat() API and a custom fetch injected into createOpenaiChat(). The fetch returns a controlled SSE Response; no network requests or real API key are needed.
- Run
bun install --frozen-lockfilein the reproduction directory. - Run
node repro.mjs(orbun run repro.mjs). - The script compares three cases sharing the same initial events:
response.createdwithstatus: "in_progress", a message/content part, and a text delta containing"The answer is ". - In the successful control, it sends the remaining text, the corresponding done events, and
response.completed, then closes the body. - In the premature-EOF case, it closes the body immediately after the initial text delta. It sends no
response.completed,response.failed,response.incomplete, or error event. - In the read-error control, it errors the body instead of closing it normally.
Actual behavior
| Case | Text received | Public terminal | Finish reason | onFinish / onError calls |
|---|---|---|---|---|
| Completed response + EOF | "The answer is 42." |
RUN_FINISHED |
stop |
1 / 0 |
| EOF before any protocol terminal | "The answer is " |
RUN_FINISHED |
stop |
1 / 0 |
| Response-body read failure | "The answer is " |
RUN_ERROR |
— | 0 / 1 |
Both runtimes reproduce these results with SDK versions 6.41.0 and 6.49.0. The script prints each result and asserts the behavior. The read-error control also produces an expected diagnostic log for the simulated failure.
The failing case is a normally exhausted HTTP response body with an incomplete application-level stream. It does not require a thrown socket/read error, user cancellation, or a model output-token limit.
This is a controlled transport reproduction; it does not claim that the official OpenAI endpoint normally omits terminal events. Socket resets, truncated HTTP bodies, or read failures may instead throw and take the error path, as the third control demonstrates. A missing terminal means the client cannot confirm completion; it does not establish whether generation failed on the server or completed there without the final events reaching the client.
Expected behavior
EOF before a recognized protocol terminal should not be reported as a confirmed successful stop. It should surface an error or another explicitly distinguishable incomplete outcome, while preserving any partial text for callers that want it.
Ensuring that every started run has a terminal event is useful, but a synthetic terminal should retain the fact that completion was never confirmed by the provider.
Relevant source
The underlying SDK's ordinary SSE iterator, used by responses.create({ stream: true }), can exhaust normally without confirming a Responses terminal event. The adapter then maps that exhaustion to RUN_FINISHED with finishReason: "stop", even though completion has not been confirmed.
The fallback in OpenAIBaseResponsesTextAdapter.processStreamChunks() explicitly handles a stream ending without response.completed; its comment even names "truncated upstream connection" as an example. If no run terminal was emitted, it synthesizes RUN_FINISHED and uses stop when there are no tool calls. Normal iterator exhaustion explains why this fallback is reached, but does not itself confirm successful generation.
I searched existing issues, PRs, and discussions. Related issue #1445 covers the opposite ordering: response.completed has arrived but EOF has not. This report covers EOF arriving without any protocol terminal. Issue #1426 concerns non-streaming structured output with an explicit finish_reason: "length", which is also a different case.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://gist.github.com/L-1ngg/cbd231c5ca9bccbe82ad387580ed6bfd
Screenshots or Videos (Optional)
Not applicable; the reproduction prints the results summarized above.
Do you intend to try to help solve this bug with your own PR?
Maybe, I'll investigate and start debugging
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
- Ngôn ngữ chính
- TypeScript
- Star
- 3.2k
- Fork
- 365
- Merge trung bình
- 1 ngày 9 giờ
- Pull request đã merge (30 ngày)
- 242
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của TanStack/ai
-
withPersistence does not save activity rows when a run is stopped or failsCó thể đã có người làm @tombeckenham đã nhận 4 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1804 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Activity events that a chat middleware emits are streamed but never recorded or persistedCó thể đã có người làm @AlemTuzlak đã nhận 4 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1803 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
withPersistence stores the durability batch hint on a stopped or failed assistant messageCó thể đã có người làm @tombeckenham đã nhận 4 ngày trước. Đang mởhas-pr waiting-on: maintainer
TanStack/ai#1802 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Discriminate status-dependent record and option types (RunRecord, SessionIndexEntry, InputState, durable-only options)Có thể đã có người làm @jherr đã nhận 5 ngày trước. Đang mởenhancement waiting-on: maintainer
TanStack/ai#1799 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-harness: an input with no principal runs with the owner session's principalCó thể đã có người làm @tombeckenham đã nhận 5 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1797 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
批量图谱关联的备份应排除Obsidian索引Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
logancyang/obsidian-copilot#3753 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area: presentation severity: important
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
voithos-labs/aragonite#763 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
fix(cursor): align Grok 4.7 context window to 256k/500k and drop unsupported max effort tierĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
diegosouzapw/OmniRoute#17386 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
💡 [Feature] Add an option to hide the empty screen background illustration (Distraction-Free)Đang mở
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 72/100
super-productivity/super-productivity#10982 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày