denoland/deno · error · Error

TLS wrap attach failed: ${attachResult}

Error message

TLS wrap attach failed: ${attachResult}

What it means

In Deno's tls_wrap internal binding, attachNativeHandle wraps an underlying native TCP or Pipe handle into the TLS layer via res.attach()/res.attachPipe(). A non-zero return value is a native error status, thrown as a plain Error including the status code ('TLS wrap attach failed: <code>'). It signals the native side refused to adopt the handle — typically an invalid, closed, or already-used handle.

Source

Thrown at ext/node/polyfills/internal_binding/tls_wrap.ts:41

        : buf.subarray(0, nread);
      res.receive(data);
    } else if (nread < 0) {
      // EOF or error - stop native TCP reads and unref the handle.
      // Without this, the libuv handle keeps a ref on the event loop
      // and prevents process exit after the TLS connection ends.
      nativeHandle.readStop();
      nativeHandle.unref();
      res.emitEof();
    }
  };
}

function attachNativeHandle(res: TLSWrap, nativeHandle: any) {
  const attachResult = nativeHandle instanceof PipeWrap
    ? res.attachPipe(nativeHandle)
    : res.attach(nativeHandle);
  if (attachResult !== 0) {
    throw new Error(`TLS wrap attach failed: ${attachResult}`);
  }

  installNativeOnread(res, nativeHandle);
  res._nativeTcpHandle = nativeHandle;
}

/**
 * Create a TLSWrap that intercepts an underlying stream handle.
 * Mirrors Node's `internalBinding('tls_wrap').wrap(handle, context, isServer)`.
 *
 * @param handle - The underlying stream handle (TCP CppGC object or JSStreamSocket handle)
 * @param context - SecureContext object { ca, cert, key, rejectUnauthorized }
 * @param isServer - Whether this is a server-side TLS connection
 * @param servername - SNI hostname for client connections
 */
function wrap(
  handle: any,
  context: any,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass a fresh, connected, unencrypted net.Socket to tls.connect({ socket })
  2. If the socket is already a TLSSocket / already encrypted, use it directly instead of wrapping again
  3. Rebuild the connection: socket.destroy(); sock = net.connect(...); then tls.connect({ socket: sock })

Example fix

// before
const tlsSock = tls.connect({ socket: pooledSocket }); // pooledSocket may be closed/reused

// after
if (pooledSocket.destroyed || pooledSocket.encrypted) {
  pooledSocket = net.connect(port, host);
}
const tlsSock = tls.connect({ socket: pooledSocket });
Defensive patterns

Strategy: try-catch

Validate before calling

const usable = socket &&
  !socket.destroyed &&
  !(socket as any).encrypted &&
  typeof socket._handle === 'object';
if (!usable) socket = net.connect(port, host);
const tlsSock = tls.connect({ socket });

Try / catch

try {
  tlsSock = tls.connect({ socket });
} catch (e: any) {
  if (/TLS wrap attach failed/.test(e?.message ?? '')) {
    socket.destroy();
    const fresh = net.connect(port, host);
    tlsSock = tls.connect({ socket: fresh });
  } else throw e;
}

Prevention

When it happens

Trigger: tls.connect({ socket }) where the net.Socket's native handle is destroyed, already attached to another TLSWrap, or absent (e.g. a socket obtained from an upgrade event or after end()); TLS-over-TLS by re-wapping an already-encrypted TLSSocket; reconnect logic reusing pooled sockets.

Common situations: Proxy/tunnel setups that tls.connect over an existing socket; connection-pool reuse where a socket was closed by the peer; wrapping sockets that Deno materialized from a JS transport instead of a real TCP handle (those take the JS-stream path instead).

Understand the failure class

Related errors


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