denoland/deno · error · Error

EPIPE

EPIPE

Error message

This socket has been ended by the other party

What it means

Deno's node:net polyfill replaces Socket.write with _writeAfterFIN (net.ts:948-989): when the peer sent a FIN the socket ends its writable side, and any later write while writableEnded is true produces genericNodeError('This socket has been ended by the other party') with code EPIPE. The callback (if given) receives the error on the next tick; otherwise the socket is destroyed with it — server-side sockets on nextTick, client sockets immediately.

Source

Thrown at ext/node/polyfills/net.ts:975

  if (!this.writableEnded) {
    return FunctionPrototypeCall(
      Duplex.prototype.write,
      this,
      chunk,
      encoding as BufferEncoding | null,
      // @ts-expect-error Using `call` seem to be interfering with the overload for write
      cb,
    );
  }

  if (typeof encoding === "function") {
    cb = encoding;
    encoding = null;
  }

  const err = genericNodeError(
    "This socket has been ended by the other party",
    { code: "EPIPE" },
  );

  if (typeof cb === "function") {
    defaultTriggerAsyncIdScope(this[asyncIdSymbol], nextTick, cb, err);
  }

  if (this._server) {
    nextTick(() => this.destroy(err));
  } else {
    this.destroy(err);
  }

  return false;
}

function _tryReadStart(socket: Socket) {
  // Not already reading, start the flow.
  debug("Socket._handle.readStart");

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Guard every write: skip when socket.writableEnded or socket.destroyed is true.
  2. Always pass a write callback (or handle the 'error' event) so the EPIPE surfaces as data, not an uncaught exception.
  3. Stop producers on 'end'/'close' — detach pipelines once the socket can no longer carry data.
  4. For request/response protocols, finish writes before the handler returns and let the framework call end().

Example fix

// before
sock.on('data', (chunk) => sock.write(ack(chunk)));
// peer sends FIN -> end() -> next write: EPIPE
// 'This socket has been ended by the other party'

// after
sock.on('data', (chunk) => {
  if (sock.writableEnded || sock.destroyed) return; // peer hung up
  sock.write(ack(chunk), (err) => { if (err) sock.destroy(); });
});
Defensive patterns

Strategy: type-guard

Type guard

/** True while the socket can still accept writes. */
function socketWritable(sock) {
  return !sock.destroyed && !sock.writableEnded && sock.writable;
}

Try / catch

sock.write(data, (err) => {
  if (err && err.code === 'EPIPE') {
    // peer already ended/destroyed the socket: stop producing and close cleanly
    stopProducers();
    sock.destroy();
  }
});

Prevention

When it happens

Trigger: Calling socket.write() after socket.end() (often triggered automatically when the 'end' event fires on FIN); an HTTP handler writing a response after the client disconnected; keepalive connections reused after the peer half-closed; pipelined writes queued behind an end().

Common situations: Slow handlers racing client aborts; proxies forwarding upstream data after the downstream closed; log-shipping clients that keep writing after the server closed the connection; missing 'close' listeners that would stop producers.

Understand the failure class

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/b7a6c456a2e7c1a9. Report an issue: GitHub.