denoland/deno · error · Error
TLS wrap attach (JS stream) failed: ${attachResult}
Error message
TLS wrap attach (JS stream) failed: ${attachResult} What it means
The JS-stream variant of the TLS attach step in Deno's tls_wrap binding: when the handle is flagged as JS-backed (JSStreamSocket wrapping a Duplex — I/O routed through JS callbacks), wrap() calls res.attachJsStream() instead of res.attach(). A non-zero status throws 'TLS wrap attach (JS stream) failed: <code>'. Encrypted output is then drained via a pull-based pump mirroring Node's TLSWrap EncOut loop.
Source
Thrown at ext/node/polyfills/internal_binding/tls_wrap.ts:100
// surfaces as an uncaught exception when the TLSSocket is created
// inside a native libuv callback (e.g. server_connection_cb) because
// there is no native TryCatch scope on the call stack.
res._initError = err;
return res;
}
const nativeHandle = handle;
res._attachNativeHandle = (nativeHandle: any) =>
attachNativeHandle(res, nativeHandle);
res._installNativeOnread = (nativeHandle: any) =>
installNativeOnread(res, nativeHandle);
if (nativeHandle[kJSStreamHandle]) {
// JS-backed stream (e.g. JSStreamSocket wrapping a Duplex).
// Use attachJsStream instead of attach -I/O goes through JS callbacks.
const attachResult = res.attachJsStream();
if (attachResult !== 0) {
throw new Error(`TLS wrap attach (JS stream) failed: ${attachResult}`);
}
// Pull-based encrypted output: instead of Rust calling a JS
// callback (which causes reentrancy issues), Rust buffers encrypted
// data and JS drains it after each operation that may produce output.
//
// The pump mirrors Node's TLSWrap::EncOut + OnStreamAfterWrite loop
// (src/crypto/crypto_tls.cc): hand encrypted bytes to the underlying
// stream, wait for that write to complete, then drive cycle() (the
// ClearIn + EncOut analog) to push any remaining cleartext, write
// more encrypted bytes if produced, and finally fire InvokeQueued for
// the cleartext WriteWrap's oncomplete. For JS-backed streams there
// is no enc_write_cb (libuv write completion) so cycle() is only
// driven here; without it the cleartext write callback never fires
// and writes deadlock (issue #33907). Gating cycle() on the
// underlying Duplex's _write callback also propagates backpressure,
// matching Node's behavior.
const jsStreamOwner = nativeHandle[kOwner];View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Ensure the underlying Duplex/transport is open and fully connected BEFORE calling tls.connect({ socket })
- Prefer a real net.Socket (native path) when the transport allows it
- On failure, re-establish the tunnel and retry the TLS wrap once with a fresh socket
Example fix
// before
const tlsSock = tls.connect({ socket: tunnelDuplex }); // tunnel not yet connected
// after
await once(tunnelDuplex, 'connect'); // ensure transport is up
const tlsSock = tls.connect({ socket: tunnelDuplex }); Defensive patterns
Strategy: try-catch
Validate before calling
if (tunnel.destroyed || tunnel.writableEnded) {
throw new Error('tunnel transport not ready for TLS attach');
}
const tlsSock = tls.connect({ socket: tunnel }); Try / catch
try {
tlsSock = tls.connect({ socket: tunnelDuplex });
} catch (e: any) {
if (/TLS wrap attach \(JS stream\) failed/.test(e?.message ?? '')) {
await reEstablishTunnel();
tlsSock = tls.connect({ socket: tunnelDuplex }); // one retry with fresh transport
} else throw e;
} Prevention
- Wait for the transport's connect/open event before wrapping it with tls.connect
- Destroy and rebuild the tunnel on close events instead of reusing stale Duplexes
- Prefer real net.Socket transports when the architecture allows
When it happens
Trigger: tls.connect over a JS-backed socket — e.g. a custom Duplex transport such as a SOCKS or HTTP-CONNECT tunnel implemented in JS — where the underlying stream is closed, not yet connected, or in a bad state at attach time.
Common situations: TLS over custom transports (proxy tunnels, SSH forwards, test doubles that substitute a Duplex for a real socket); race where the tunnel closes before tls.connect attaches.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- TLS wrap attach failed: ${attachResult}
- ERR_INVALID_FD_TYPE
- ERR_INVALID_ARG_TYPE
- fetch failed
- Either `node` or `range` must be provided when reporting an
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/e7d0063e555d56c6.
Report an issue: GitHub.