Error: write EPIPE

Node.js · updated 2026-11-03

Emitted as an 'error' event on a stream or socket when you write after the reader went away: a client that disconnected mid-response, a child process that exited, or stdout piped into a command like head that stopped reading. Without an error listener it crashes the process.

node:events:497
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:161:15)
    at writeGeneric (node:internal/stream_base_commons:152:3)
    at Socket._writeGeneric (node:net:953:11)
    at Socket._write (node:net:965:8) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

Quick fix

Add an 'error' listener to every stream and child process stdin you write to, and stop writing once the other side has closed. Use stream.pipeline() so errors and early closes on either end are handled together. For CLI output piped to head, exit quietly on EPIPE.

  1. Find which stream wrote: a response socket, child.stdin, or process.stdout.
  2. Attach an 'error' handler (or use stream.pipeline / stream/promises pipeline) so EPIPE does not crash the process.
  3. Check writable state before writing (socket.destroyed, res.writableEnded), and stop on close.
  4. For child processes, wait for them to be ready and handle their exit before writing more input.
  5. For CLIs, treat EPIPE on stdout as a normal end: process.stdout.on('error', (e) => e.code === 'EPIPE' && process.exit(0)).
typescript
import { pipeline } from 'node:stream/promises';
import { createReadStream } from 'node:fs';

app.get('/export', async (req, res) => {
  try {
    // pipeline handles a client disconnect (EPIPE / ERR_STREAM_PREMATURE_CLOSE) cleanly
    await pipeline(createReadStream('/data/export.csv'), res);
  } catch (err: any) {
    if (err.code !== 'EPIPE' && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') throw err;
  }
});

Why it happens

The client disconnected mid-response

A user closing the tab, a load balancer timing out or a mobile network dropping during a large download closes the socket while the server is still writing.

A child process exited early

Writing to child.stdin after ffmpeg, a shell command or a worker has crashed or finished raises EPIPE on the next write.

Output piped to a command that stops reading

node script.js | head -5 closes the pipe after five lines; the script keeps writing to stdout and gets EPIPE.

Writing after end() or destroy()

Code paths that keep writing after the stream was ended by another branch (a timeout, an error handler) trip over a closed socket.

Other fixes

  • Client disconnects are normal for downloads and streaming: log them at info level instead of reporting them as errors.
  • Set server timeouts (keepAliveTimeout, headersTimeout) consistently with your load balancer to reduce mid-response closes.

Same error, other wording

  • Error: write EPIPE at WriteWrap.onWriteComplete
  • Error [ERR_STREAM_PREMATURE_CLOSE]: Premature close
  • Error: This socket has been ended by the other party

Let your coding agent catch the next one

Runheed records this error in production with the stack trace, the breadcrumbs before it and the agent session that shipped the code, and serves it to Claude Code, Cursor or any MCP client. With it connected, you can ask:

> Find the EPIPE errors in production, show me which stream or child process was being written each time, and add error handling or pipeline() so client disconnects stop crashing or alerting.

Plans from $29/moSee what your agent readsSee the setupCancel any time from the billing portal

Related errors

References: Node.js errors: common system errors (EPIPE), Node.js: stream.pipeline