Terminate OpenAI streams when response.failed arrives - #6920
Conversation
🦋 Changeset detectedLatest commit: 383fdc3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 30 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Important
The reproduction test is well-constructed, but the implementation fix it's meant to validate is missing from this PR. Both terminal predicates need updating, and the WebSocket path needs a matching test.
Reviewed changes
A new reproduction test for the bug where response.failed SSE events do not terminate an OpenAI Responses API stream.
OpenAiFailedStreamNonterminationAudit.test.ts: One test that sends aresponse.failedSSE event intocreateResponseStreamand asserts the stream terminates rather than hanging indefinitely.
⚠️ Implementation fix missing
The terminal predicates that cause this bug are still unchanged:
OpenAiClient.ts:271-274(HTTP SSE):Stream.takeUntilchecks onlyresponse.completedandresponse.incomplete—response.failedis absent.OpenAiClient.ts:638-643(WebSocket): Same omission — thedoneflag is only set forresponse.completedandresponse.incomplete.
Both need e.type === "response.failed" added to their predicates.
Technical details
# Add `response.failed` to both terminal predicates
## Affected sites
- `packages/ai/openai/src/OpenAiClient.ts:271` — SSE `takeUntil` predicate
- `packages/ai/openai/src/OpenAiClient.ts:640` — WebSocket `takeUntil` predicate
## Required outcome
Both terminal predicates must treat `response.failed` as a terminal event alongside `response.completed` and `response.incomplete`, so the stream completes promptly when that event arrives.
## Suggested approach
Change `event.data.type === "response.completed" || event.data.type === "response.incomplete"` to include `event.data.type === "response.failed"` (and the equivalent in the WebSocket path).⚠️ Missing WebSocket test
The PR description identifies this bug in both HTTP SSE and WebSocket streams (OpenAiClient.ts:638-643), but the reproduction test only covers the SSE path. A corresponding WebSocket test should be added.
ℹ️ Nitpicks
it.liveis unique in this codebase (all existing AI provider tests useit.effect). It's the right tool here —Effect.timeoutOptionneeds a real clock — but worth calling out. An alternative is to useTestClockand explicitly advance it in ait.effecttest, which would keep runner isolation but is arguably more verbose.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes since the prior pullfrog review (14ffdf0):
- Added
response.failedto both terminal predicates — the SSEtakeUntiland WebSocketdoneflag inOpenAiClient.tsnow treatresponse.failedas a terminal event alongsideresponse.completedandresponse.incomplete. - Integrated SSE test and added a WebSocket test — the standalone audit test file was deleted; both tests now live in
OpenAiClient.test.tsunderdescribe("createResponseStream"). - Extended test infrastructure — a
keepOpenflag onMockResponseSSE variants andProxy-based stream replacement inmakeResponseenable timeout-based assertions for non-terminating streams.
DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|

Summary
Valid failed Responses API streams can wait indefinitely, and WebSocket streams can retain their semaphore after the terminal failure event.
Important
This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.
response.failed does not terminate OpenAI streams
Module:
ai/openai/OpenAiClientAudit ID:
adapters-ai-6-openai-failed-stream-nonterminationSeverity / confidence: high / high
What happens
Valid failed Responses API streams can wait indefinitely, and WebSocket streams can retain their semaphore after the terminal failure event.
Why it happens
Both the HTTP SSE and WebSocket terminal predicates omit response.failed.
Expected behavior
response.failed is a terminal Responses API event alongside response.completed and response.incomplete.
Relevant implementation
These links and excerpts are pinned to audit base
c9b56ab507f224426ee8388dc450da447ec4715f.packages/ai/openai/src/OpenAiClient.ts:268-275packages/ai/openai/src/OpenAiClient.ts:638-643View problematic code at
packages/ai/openai/src/OpenAiClient.ts:268-275View exact lines on GitHub
View problematic code at
packages/ai/openai/src/OpenAiClient.ts:638-643View exact lines on GitHub
Reproduction
pnpm test --run packages/ai/openai/test/OpenAiFailedStreamNonterminationAudit.test.tsObserved failure: The response.failed stream timed out instead of completing.
Implementation handoff
The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.
pnpm test --run packages/ai/openai/test/OpenAiFailedStreamNonterminationAudit.test.tsAudit provenance
c9b56ab507f224426ee8388dc450da447ec4715f8f9499f562729f5f7b08d8bcc4db86b4aeff8a21adapters-ai-6-openai-failed-stream-nonterminationCloses EFF-339