denoland/deno · error · TypeError
Socket has no handle - cannot upgrade
Error message
Socket has no handle - cannot upgrade
What it means
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.
Source
Thrown at ext/http/02_websocket.ts:162
const nodeSocket = options.socket;
// Build the 101 response from r.headerList so the headers stay in
// sync with the header-list built above (protocol negotiation, etc.).
let responseHead = "HTTP/1.1 101 Switching Protocols\r\n";
for (let i = 0; i < r.headerList.length; i++) {
const { 0: name, 1: value } = r.headerList[i];
responseHead += `${name}: ${value}\r\n`;
}
responseHead += "\r\n";
if (nodeSocket.destroyed) {
throw new TypeError(
"Socket is already destroyed - cannot upgrade to WebSocket",
);
}
const handle = nodeSocket._handle;
if (!handle) {
throw new TypeError("Socket has no handle - cannot upgrade");
}
if (typeof handle.takeStream !== "function") {
throw new TypeError(
"Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket",
);
}
// Extra bytes that were already buffered (e.g., from the upgrade
// request body that arrived with the headers)
const extraBytes = options.head || new Uint8Array(0);
// Defer setup so the caller can attach event handlers (onopen,
// onmessage, etc.) before events fire.
(async () => {
try {
// Wait for the 101 response to fully flush before taking the
// stream. A fire-and-forget write could leave data in the
// internal_write_queue that would be orphaned once we detachView on GitHub (pinned to 89f33cbef2)
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.
Example fix
// before
server.on("upgrade", (req, socket, head) => {
const { response } = Deno.upgradeWebSocket(req, { socket, head }); // _handle already null
});
// after
server.on("upgrade", (req, socket, head) => {
if (socket.destroyed || !socket._handle) return socket.destroy();
const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });
ws.on("open", () => console.log("ws open"));
}); Defensive patterns
Strategy: validation
Validate before calling
server.on("upgrade", (req, socket, head) => {
if (socket.destroyed || !(socket as any)._handle) return socket.destroy();
Deno.upgradeWebSocket(req, { socket, head });
}); Type guard
function hasNodeHandle(s: unknown): boolean { const sock = s as { _handle?: unknown } | null; return !!sock && !!sock._handle; } Try / catch
try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes("no handle")) { socket.destroy(); return; } throw e; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Socket is already destroyed - cannot upgrade to WebSocket
- Socket is not a TCP socket - only TCP connections can be upg
- Already upgraded
- Unsupported 'alpnProtocols' option provided. 'h2' and 'http/
- Invalid Header: 'upgrade' header must contain 'websocket'
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/3008da4caf609962.
Report an issue: GitHub.