Node.js "ERR_STREAM_PREMATURE_CLOSE" - Fix Stream Closed Early
A readable or writable stream was destroyed before it reached its natural end. pipeline() and finished() surface this as ERR_STREAM_PREMATURE_CLOSE - commonly from an aborted HTTP request or a peer that dropped the connection.
What this error means
A download, upload, or stream.pipeline() rejects with ERR_STREAM_PREMATURE_CLOSE. In CI it can be intermittent - the same job passes on retry - when the cause is a flaky connection rather than a logic bug.
Error [ERR_STREAM_PREMATURE_CLOSE]: Premature close
at IncomingMessage.onclose (node:internal/streams/end-of-stream:154:30)
at IncomingMessage.emit (node:events:519:28)
code: 'ERR_STREAM_PREMATURE_CLOSE'Common causes
The connection dropped mid-stream
A network blip, an aborted request, or a server that closed the socket early ends the stream before completion. When transient, it passes on retry.
A stream destroyed without finishing
Code that calls destroy() (or returns early) on one end of a pipeline before the data has flushed triggers a premature close on the other end.
How to fix it
Use pipeline and retry transient closes
Prefer stream.pipeline() so cleanup is handled, and retry the operation when the close is caused by a flaky connection.
import { pipeline } from 'node:stream/promises';
async function download() {
for (let attempt = 1; attempt <= 3; attempt++) {
try { return await pipeline(source(), dest()); }
catch (err) {
if (err.code !== 'ERR_STREAM_PREMATURE_CLOSE' || attempt === 3) throw err;
}
}
}Stop destroying the stream early
- Check for an early
returnordestroy()that abandons a stream before it finishes. - Honour
AbortSignalcleanly rather than tearing the socket down mid-write. - Wait for the
finish/closeevent before treating the transfer as done.
How to prevent it
- Use
stream.pipeline()so partial failures clean up consistently. - Retry premature-close failures that stem from transient network drops.
- Avoid destroying a stream until its peer has flushed.