nodejs/node · error · SocketError
UND_ERR_SOCKET
UND_ERR_SOCKET
Error message
destroyed
What it means
Thrown inside connectH1() (the HTTP/1.1 socket setup in client-h1.js) as a SocketError (code UND_ERR_SOCKET) with message 'destroyed'. After attaching a socket to the client, connectH1 inspects socket.errored and socket.destroyed; if the socket was already torn down (destroyed === true) it cannot be wired to a parser and is rejected. This usually surfaces the real socket lifecycle problem as a typed error.
Source
Thrown at deps/undici/src/lib/dispatcher/client-h1.js:873
/**
* @param {import ('./client.js')} client
* @param {import('net').Socket} socket
* @returns
*/
function connectH1 (client, socket) {
client[kSocket] = socket
if (!llhttpInstance) {
llhttpInstance = lazyllhttp()
}
if (socket.errored) {
throw socket.errored
}
if (socket.destroyed) {
throw new SocketError('destroyed')
}
socket[kNoRef] = false
socket[kWriting] = false
socket[kReset] = false
socket[kBlocking] = false
socket[kIdleSocketValidation] = 0
socket[kIdleSocketValidationTimeout] = null
socket[kSocketUsed] = false
socket[kParser] = new Parser(client, socket, llhttpInstance)
util.addListener(socket, 'error', onHttpSocketError)
util.addListener(socket, 'readable', onHttpSocketReadable)
util.addListener(socket, 'end', onHttpSocketEnd)
util.addListener(socket, 'close', onHttpSocketClose)
socket[kClosed] = false
socket.on('close', onSocketClose)View on GitHub (pinned to 1b2de5e052)
Solutions
- Do not dispatch on a destroyed/closing client — check client.closed / client.destroyed first.
- If you aborted, await client.close() or the abort completion before reusing the client.
- Investigate the upstream socket.errored (it is thrown when present) — TLS/cert or ECONNREFUSED often cause the destroy.
- Retry the request on a fresh client when UND_ERR_SOCKET/destroyed is caught, ideally via a RetryAgent.
Example fix
// before
client.destroy()
await client.request({ method: 'GET', path: '/' }) // may throw 'destroyed'
// after
await client.close()
const client2 = new Client(origin)
await client2.request({ method: 'GET', path: '/' }) Defensive patterns
Strategy: retry
Validate before calling
function isUsable(client) {
return !client.closed && !client.destroyed
}
if (!isUsable(client)) client = new Client(origin) Type guard
const isSocketDestroyed = (e) => e?.code === 'UND_ERR_SOCKET' && /destroyed/.test(e.message ?? '')
Try / catch
try {
await client.request(req)
} catch (e) {
if (e?.code === 'UND_ERR_SOCKET') {
// recreate client and retry idempotent requests once
} else throw e
} Prevention
- Never dispatch on a destroyed/closing client; await close() first.
- Use RetryAgent to replay idempotent requests on socket errors.
- Investigate socket.errored — the underlying TLS/connect failure is the real cause.
When it happens
Trigger: A connection attempt that completes after the socket was destroyed (e.g. an aborted request, a TLS error, a timeout firing first, or a manual client.destroy() racing the connect); reuse of a socket that the pool already torn down.
Common situations: Calling client.destroy() then dispatching before close completes; an interceptor aborting the request mid-connect; keep-alive sockets reaped by the server sent FIN and were destroyed locally just as a new request picked them; Node shutdown aborting in-flight connects.
Related errors
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/252bea18358e235a.
Report an issue: GitHub.