Streams cancel the response body at [DONE], so keep-alive connections are never reused (regression in 7.5.0)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 54/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- node.js, typescript
- Domain
- backend, networking
Research direction
The report identifies the stream iterator’s handling of data: [DONE] and response body cleanup, and links regression #2317. Read that change and inspect the stream cleanup path; reproduce with the supplied local HTTP server, which delays ending the chunked response after [DONE]. Done means streamed requests can reuse keep-alive connections in the normal case while still terminating when the response body remains open.
Written by the indexing model from the issue text.
Description
Confirm this is a Node library issue and not an underlying OpenAI API issue
- This is an issue with the Node library
Describe the bug
Since v7.5.0 (#2317, "finish SSE streams immediately after [DONE]"), every streamed request uses a new TCP/TLS connection. Before, one keep-alive connection was reused.
When the iterator sees data: [DONE], it breaks out of the loop, and the cleanup cancels the response body (reader.cancel()). For a well-behaved server, the end of the chunked HTTP response arrives a few milliseconds after [DONE], but the SDK has not read it yet. undici cannot return a connection with unread bytes to the pool, so it destroys the socket. The next request has to do a new TCP + TLS handshake.
Side effects we observed with Node 24 (bundled undici):
- one extra handshake per streamed call: ~80 ms from a home connection, ~10–20 ms from a datacenter, against OpenRouter;
- one
AbortErrorper stream, captured by APM agents (elastic-apm) even though the SDK swallows it; - 2 connections per stream: after the abort, undici re-queues the request and opens a new socket before it checks
request.aborted, then closes it right away.
I understand the goal of #2317: not hanging when a server or proxy keeps the response open after [DONE]. A possible fix that keeps that guarantee: after [DONE], keep reading (and discarding) the rest of the body for a short time or size budget (e.g. 1 s / 64 KB). Only cancel if the body has not ended by then. Then the connection goes back to the pool in the normal case.
As far as I can see, no existing issue covers this.
To Reproduce
- Save the snippet below as
repro.mjsin an empty ESM project ("type": "module"). npm i [email protected] && node repro.mjs→10 sequential streams -> 20 TCP connectionsnpm i [email protected] && node repro.mjs→10 sequential streams -> 1 TCP connections
Results:
| openai | connections for 10 streams |
|---|---|
| 6.49.0 | 1 |
| 7.4.0 | 1 |
| 7.5.0 | 20 |
| 7.44.0 | 20 |
Code snippets
import http from 'node:http';
import OpenAI from 'openai';
import { VERSION } from 'openai/version';
// Minimal OpenAI-compatible SSE server: a few chunks, `data: [DONE]`,
// then a normal end of the chunked response a few ms later.
let connections = 0;
const server = http.createServer((req, res) => {
req.resume();
req.on('end', () => {
res.writeHead(200, { 'content-type': 'text/event-stream' });
for (const word of ['Hello', ' world']) {
res.write(`data: ${JSON.stringify({ id: 'x', object: 'chat.completion.chunk', created: 0, model: 'm', choices: [{ index: 0, delta: { content: word }, finish_reason: null }] })}\n\n`);
}
res.write('data: [DONE]\n\n');
setTimeout(() => res.end(), 5);
});
});
server.on('connection', () => connections++);
await new Promise(r => server.listen(0, r));
const client = new OpenAI({ apiKey: 'test', baseURL: `http://127.0.0.1:${server.address().port}/v1`, maxRetries: 0 });
const calls = 10;
for (let i = 0; i < calls; i++) {
const stream = await client.chat.completions.create({ model: 'm', stream: true, messages: [{ role: 'user', content: 'hi' }] });
for await (const _ of stream) {}
await new Promise(r => setTimeout(r, 50));
}
console.log(`openai ${VERSION} (node ${process.version}): ${calls} sequential streams -> ${connections} TCP connections`);
server.close();
OS
macOS
Node version
v24.18.0
Library version
openai v7.44.0 (regression since v7.5.0)
- Dominant language
- TypeScript
- Stars
- 11.3k
- Forks
- 1.6k
- Avg merge
- 12h 40m
- Merged PRs (30d)
- 204
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openai/openai-node
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
openai/openai-node#3124 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
openai/openai-node#3080 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
openai/openai-node#3077 ·
Maintainers usually reply within 1 day
-
Split custom-code bootstrap tests into independently runnable casesPossibly taken @jbeckwith-oai claimed this 11 days ago. Open
openai/openai-node#3058 · 1 assignee ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 58/100
openai/openai-node#2962 · 1 comment ·
Maintainers usually reply within 1 day
All issues in openai/openai-node
Similar issues
-
bug needs-triage potential-regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
aws/aws-sdk-js-v3#8408 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
colinhacks/zod#6743 ·
Maintainers usually reply within 1 day
-
[BUG] Packing list: the weight field drops digits on a slow connection and saves the wrong weightOpen
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
anthropics/anthropic-sdk-typescript#1296 ·
Maintainers usually reply within 8 days
-
fix(docs): align git__gh troubleshooting with its actual failure pathsPossibly taken @akhil9tiet claimed this today. Openbug docs p2
Difficulty 1/5 1-3 hours Newbie friendliness 78/100
vercel/eve#6130 · 2 comments ·
Maintainers usually reply within 1 day