OpenAI Responses: EOF without a terminal event is reported as a successful stop
@tombeckenham ci sta già lavorando.
Dal 22/9/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
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.
- Lingua principale
- TypeScript
- Stelle
- 3.1k
- Fork
- 331
- Merge medio
- 2g 6h
- PR unite (30g)
- 155
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/ai
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
-
waiting-on: maintainer
-
Client tools never execute against spec-compliant AG-UI servers (plain `success` RUN_FINISHED) Apertawaiting-on: maintainer
-
waiting-on: maintainer
Difficoltà 3/5 1-2 giorni Idoneità per principianti 70/100
-
waiting-on: maintainer
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
receptron/mulmoterminal#2264 ·
-
documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
components-web-app/docs#96 ·
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
simonsobs/tileviewer#114 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100