Standalone repro — nodejs/node#63989
ERR_STREAM_PREMATURE_CLOSE regression introduced in Node v24.17.0
(suspected: "http: fix response queue poisoning in http.Agent").
Surfaces in the wild as node-fetch / gaxios / google-auth "Premature close".
npm install # node-fetch@2
node repro.cjsNo external services — the script starts its own localhost HTTPS server (self-signed cert embedded in the file).
# Node v24.17.0
❌ REPRODUCED — 10/10 premature closes (bug present on v24.17.0)
# Node v24.16.0
✅ NO premature closes (clean on v24.16.0)
Tested deterministic: 10/10 fail on v24.17.0, 10/10 clean on v24.16.0.
The local server answers like an istio-envoy HTTP/1.1 gateway:
| Ingredient | Required? |
|---|---|
| TLS (HTTPS) | Yes — plain HTTP does not reproduce |
content-encoding: gzip |
Yes |
transfer-encoding: chunked |
Yes |
| keep-alive | Yes (HTTP/1.1 default) |
| large body (~2MB across many writes) | Yes — small bodies don't trip it |
| node-fetch@2's body consumption | Yes — see below |
The failure requires node-fetch@2's body-handling path. A naive core
client — res.pipe(zlib.createGunzip()), drained, awaited with
stream.finished() — is clean on both v24.16.0 and v24.17.0, so it does
not isolate the regression. Likewise a core client that just does
res.resume() + finished(res) (no gunzip) passes on both. Something in how
node-fetch attaches to / drains the response stream is what exposes the
truncated body as ERR_STREAM_PREMATURE_CLOSE.
REQUESTS(default 10) — number of sequential requestsBODY_BYTES(default 2_000_000) — uncompressed body size
Pin to Node 24.16.0 until the regression is fixed upstream (or until
the affected code moves off the unmaintained node-fetch@2 / gaxios@6
stack).