{"record":{"id":"3008da4caf609962","repo":"denoland/deno","slug":"socket-has-no-handle-cannot-upgrade","errorCode":null,"errorMessage":"Socket has no handle - cannot upgrade","messagePattern":"Socket has no handle - cannot upgrade","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/02_websocket.ts","lineNumber":162,"sourceCode":"    const nodeSocket = options.socket;\n\n    // Build the 101 response from r.headerList so the headers stay in\n    // sync with the header-list built above (protocol negotiation, etc.).\n    let responseHead = \"HTTP/1.1 101 Switching Protocols\\r\\n\";\n    for (let i = 0; i < r.headerList.length; i++) {\n      const { 0: name, 1: value } = r.headerList[i];\n      responseHead += `${name}: ${value}\\r\\n`;\n    }\n    responseHead += \"\\r\\n\";\n\n    if (nodeSocket.destroyed) {\n      throw new TypeError(\n        \"Socket is already destroyed - cannot upgrade to WebSocket\",\n      );\n    }\n    const handle = nodeSocket._handle;\n    if (!handle) {\n      throw new TypeError(\"Socket has no handle - cannot upgrade\");\n    }\n    if (typeof handle.takeStream !== \"function\") {\n      throw new TypeError(\n        \"Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket\",\n      );\n    }\n\n    // Extra bytes that were already buffered (e.g., from the upgrade\n    // request body that arrived with the headers)\n    const extraBytes = options.head || new Uint8Array(0);\n\n    // Defer setup so the caller can attach event handlers (onopen,\n    // onmessage, etc.) before events fire.\n    (async () => {\n      try {\n        // Wait for the 101 response to fully flush before taking the\n        // stream. A fire-and-forget write could leave data in the\n        // internal_write_queue that would be orphaned once we detach","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/02_websocket.ts#L144-L180","documentation":"Also on the node:http upgrade path: after confirming the socket is alive, Deno reads nodeSocket._handle (the libuv stream handle) to take over the raw TCP stream via takeStream(). If _handle is null/absent - typically because the handle was already closed/detached - there is nothing to take and the upgrade is refused.","triggerScenarios":"Passing a socket whose libuv handle was already closed (net.Socket after an error where Node nulls _handle); passing a mock/socket-like object in tests that lacks _handle; passing a socket obtained from a different runtime or an already-consumed socket from a previous upgrade attempt.","commonSituations":"Unit tests substituting fake sockets for node net.Socket; error paths where socket.destroy() ran (destroy clears the handle) before the guard-visible destroyed flag; interop layers constructing sockets manually; racing a second upgrade on the same socket after the first detached the handle.","solutions":["Only pass genuine, live node sockets from the 'upgrade' event; guard with socket?._handle before calling.","Ensure nothing destroyed/closed the socket earlier in the pipeline (destroyed check alone is not enough - handle can be gone).","In tests, use real sockets (createServer + client connect) rather than mocks."],"exampleFix":"// before\nserver.on(\"upgrade\", (req, socket, head) => {\n  const { response } = Deno.upgradeWebSocket(req, { socket, head }); // _handle already null\n});\n\n// after\nserver.on(\"upgrade\", (req, socket, head) => {\n  if (socket.destroyed || !socket._handle) return socket.destroy();\n  const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });\n  ws.on(\"open\", () => console.log(\"ws open\"));\n});","handlingStrategy":"validation","validationCode":"server.on(\"upgrade\", (req, socket, head) => {\n  if (socket.destroyed || !(socket as any)._handle) return socket.destroy();\n  Deno.upgradeWebSocket(req, { socket, head });\n});","typeGuard":"function hasNodeHandle(s: unknown): boolean { const sock = s as { _handle?: unknown } | null; return !!sock && !!sock._handle; }","tryCatchPattern":"try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes(\"no handle\")) { socket.destroy(); return; } throw e; }","preventionTips":["Pass only genuine sockets from the 'upgrade' event, never mocks or reused sockets.","Test with real TCP connections, not fake socket objects.","Remember destroy() can clear _handle even when destroyed looks false in edge races."],"tags":["websocket","node-compat","libuv","net-socket"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}