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
- Guard every write: skip when socket.writableEnded or socket.destroyed is true.
- Always pass a write callback (or handle the 'error' event) so the EPIPE surfaces as data, not an uncaught exception.
- Stop producers on 'end'/'close' — detach pipelines once the socket can no longer carry data.
- 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
- Check socket.writableEnded/destroyed before every write
- Always pass a write callback or subscribe to 'error' so EPIPE is data, not a crash
- Stop producers on 'end' and 'close' events
- Finish response writes inside the handler; never write from timers after the peer may have gone
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
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- ERR_INVALID_ARG_VALUE
- ERR_FS_CP_SOCKET
- ERR_HTTP2_SOCKET_BOUND
- unknown override kind: ${override.kind}
- ERR_MISSING_ARGS
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/b7a6c456a2e7c1a9.
Report an issue: GitHub.