fix: emit stream finalization errors#1972
Conversation
Resolves #553
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13a871bdba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
jbeckwith-oai
left a comment
There was a problem hiding this comment.
Comprehensive review completed; no blocking or nonblocking findings.
Reproduction: I applied this PR’s regression test to base commit 95b54e58 and ran jest tests/lib/ChatCompletionStream.test.ts --runInBand -t "emits finalization failures as errors". The original behavior reproduced exactly: _emitFinal() threw OpenAIError: stream ended without producing a ChatCompletionMessage with role=assistant, the error did not reach the stream error path, done() remained pending, and the test failed after the 30s timeout.
Validation on head 1e9094e8:
jest tests/lib/ChatCompletionStream.test.ts --runInBand: 6/6 passed, including the malformed user-role-only stream regression.jest tests/lib --runInBand: 215/215 passed across 15 suites.- Direct EventStream probes confirmed synchronous executor throws, asynchronous executor rejections, and
_emitFinal()throws each emit exactly oneerror, thenend, and rejectdone()with the matching message; the success path still emitsendand resolvesdone(). - A throwing
endlistener still surfaces independently whiledone()remains resolved anderroredremains false, so follow-up commit1e9094e8correctly preserves pre-existing end-listener behavior and fixes the concern raised on the first revision. - CI is green across Node 20/22/24/26, build, lint, examples, ecosystem, CodeQL, Agents SDK regression, and detect-breaking-changes; OkTest reports 237/237 SDK tests passed.
Compatibility: the patch changes no public API, exports, types, or data shapes. The shared EventStream change is behaviorally scoped to routing executor/finalization failures through the already documented error lifecycle. The extra microtask remains inside the existing deferred setTimeout startup and does not reorder observable stream events. Existing error normalization/cause preservation is retained. The only newly handled cases are the reported finalization rejection and synchronous executor throws; end listener exceptions are intentionally outside the handler. This is an appropriate bug fix with low breaking-change risk.
Summary
Root cause
EventStream._run()handled executor rejections, but_emitFinal()ran inside a fulfilled.then()callback whose returned promise was ignored. If finalization threw, such as when no assistant message was produced, the rejection escaped instead of emitting the stream'serrorevent.Impact
Consumers using
.on('error', ...)now receive finalization failures consistently, and awaitingdone()rejects with the same error rather than leaving an unhandled rejection that can crash the process.Validation
pnpm run formatpnpm exec tsc --noEmitpnpm exec jest tests/lib/ChatCompletionStream.test.ts --runInBandgit diff --checkResolves #553