{"record":{"id":"e7d0063e555d56c6","repo":"denoland/deno","slug":"tls-wrap-attach-js-stream-failed-attachresult","errorCode":null,"errorMessage":"TLS wrap attach (JS stream) failed: ${attachResult}","messagePattern":"TLS wrap attach \\(JS stream\\) failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal_binding/tls_wrap.ts","lineNumber":100,"sourceCode":"    // surfaces as an uncaught exception when the TLSSocket is created\n    // inside a native libuv callback (e.g. server_connection_cb) because\n    // there is no native TryCatch scope on the call stack.\n    res._initError = err;\n    return res;\n  }\n\n  const nativeHandle = handle;\n  res._attachNativeHandle = (nativeHandle: any) =>\n    attachNativeHandle(res, nativeHandle);\n  res._installNativeOnread = (nativeHandle: any) =>\n    installNativeOnread(res, nativeHandle);\n\n  if (nativeHandle[kJSStreamHandle]) {\n    // JS-backed stream (e.g. JSStreamSocket wrapping a Duplex).\n    // Use attachJsStream instead of attach -I/O goes through JS callbacks.\n    const attachResult = res.attachJsStream();\n    if (attachResult !== 0) {\n      throw new Error(`TLS wrap attach (JS stream) failed: ${attachResult}`);\n    }\n\n    // Pull-based encrypted output: instead of Rust calling a JS\n    // callback (which causes reentrancy issues), Rust buffers encrypted\n    // data and JS drains it after each operation that may produce output.\n    //\n    // The pump mirrors Node's TLSWrap::EncOut + OnStreamAfterWrite loop\n    // (src/crypto/crypto_tls.cc): hand encrypted bytes to the underlying\n    // stream, wait for that write to complete, then drive cycle() (the\n    // ClearIn + EncOut analog) to push any remaining cleartext, write\n    // more encrypted bytes if produced, and finally fire InvokeQueued for\n    // the cleartext WriteWrap's oncomplete. For JS-backed streams there\n    // is no enc_write_cb (libuv write completion) so cycle() is only\n    // driven here; without it the cleartext write callback never fires\n    // and writes deadlock (issue #33907). Gating cycle() on the\n    // underlying Duplex's _write callback also propagates backpressure,\n    // matching Node's behavior.\n    const jsStreamOwner = nativeHandle[kOwner];","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal_binding/tls_wrap.ts#L82-L118","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst tlsSock = tls.connect({ socket: tunnelDuplex }); // tunnel not yet connected\n\n// after\nawait once(tunnelDuplex, 'connect'); // ensure transport is up\nconst tlsSock = tls.connect({ socket: tunnelDuplex });","handlingStrategy":"try-catch","validationCode":"if (tunnel.destroyed || tunnel.writableEnded) {\n  throw new Error('tunnel transport not ready for TLS attach');\n}\nconst tlsSock = tls.connect({ socket: tunnel });","typeGuard":null,"tryCatchPattern":"try {\n  tlsSock = tls.connect({ socket: tunnelDuplex });\n} catch (e: any) {\n  if (/TLS wrap attach \\(JS stream\\) failed/.test(e?.message ?? '')) {\n    await reEstablishTunnel();\n    tlsSock = tls.connect({ socket: tunnelDuplex }); // one retry with fresh transport\n  } else throw e;\n}","preventionTips":["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"],"tags":["tls","network","node-compat","sockets","proxy"],"backgroundTag":"tls-wrap-attach-failed","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}