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

  1. Do not dispatch on a destroyed/closing client — check client.closed / client.destroyed first.
  2. If you aborted, await client.close() or the abort completion before reusing the client.
  3. Investigate the upstream socket.errored (it is thrown when present) — TLS/cert or ECONNREFUSED often cause the destroy.
  4. 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

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.