Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

OpenAI Responses: EOF without a terminal event is reported as a successful stop

クローズ
#1,447 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@tombeckenham がすでに取り組んでいます。

2026年9月22日 から。

評価

この issue はまだ評価されていません。

説明

waiting-on: maintainer
TanStack AI version
  • @tanstack/ai: 0.58.0
  • @tanstack/ai-openai: 0.23.1
  • @tanstack/openai-base: 0.10.15
  • Underlying openai SDK: 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.

  1. Run bun install --frozen-lockfile in the reproduction directory.
  2. Run node repro.mjs (or bun run repro.mjs).
  3. The script compares three cases sharing the same initial events: response.created with status: "in_progress", a message/content part, and a text delta containing "The answer is ".
  4. In the successful control, it sends the remaining text, the corresponding done events, and response.completed, then closes the body.
  5. 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.
  6. 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.
主要言語
TypeScript
スター
3.1k
フォーク
340
平均マージ
2日 7時間
マージ済み PR(30日)
175

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

TanStack/ai のほかの issue

TanStack/ai の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。