denoland/deno · error · TypeError
Socket is already destroyed - cannot upgrade to WebSocket
Error message
Socket is already destroyed - cannot upgrade to WebSocket
What it means
On the node:http upgrade path of Deno.upgradeWebSocket (when options.socket is a node net.Socket received from a server 'upgrade' event), Deno must write the 101 response and detach the TCP stream from libuv. It refuses if nodeSocket.destroyed is already true, because a destroyed socket cannot carry the WebSocket traffic.
Source
Thrown at ext/http/02_websocket.ts:156
}
})();
} else if (options.socket) {
// node:http upgrade path: the socket is a node net.Socket from the
// "upgrade" event. Write the 101 response, take the TCP stream from
// libuv, and create a WebSocket over it via ext/http.
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,View on GitHub (pinned to 89f33cbef2)
Solutions
- Check socket.destroyed before upgrading and bail out (close/ignore) instead of throwing.
- Perform the upgrade synchronously in the 'upgrade' event handler, not after awaiting something.
- Remove middleware/error paths that destroy upgrade sockets before the handler runs.
- Raise or disable the relevant socket timeouts if long setup is required.
Example fix
// before
server.on("upgrade", (req, socket) => {
socket.destroy(); // e.g. auth failure path ran first
const { response } = Deno.upgradeWebSocket(req, { socket, head });
});
// after
server.on("upgrade", async (req, socket, head) => {
if (socket.destroyed) return; // guard
const { socket: ws, response } = Deno.upgradeWebSocket(req, { socket, head });
ws.on("message", (m) => console.log(m));
}); Defensive patterns
Strategy: validation
Validate before calling
server.on("upgrade", (req, socket, head) => {
if (socket.destroyed) return; // client gone / earlier destroy
const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });
}); Type guard
function isLiveNodeSocket(s: unknown): boolean { const sock = s as { destroyed?: boolean; _handle?: unknown } | null; return !!sock && sock.destroyed !== true; } Try / catch
try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes("already destroyed")) { socket.destroy(); return; } throw e; } Prevention
- Upgrade synchronously inside the 'upgrade' event, before any await.
- Audit error/middleware paths so nothing destroys upgrade sockets first.
- Attach a 'close'/'error' listener on the socket to short-circuit late upgrades.
When it happens
Trigger: Calling Deno.upgradeWebSocket(req, { socket }) inside a server.on('upgrade') handler after the socket timed out, errored, or was explicitly destroyed (e.g. in a prior error branch or by middleware that closes sockets); deferring the upgrade asynchronously until after disconnect; client disconnecting between the upgrade event and the handler body.
Common situations: Express/Node-compat servers where earlier middleware or error handlers call socket.destroy()/res.socket.destroy(); idle-timeout policies (server.headersTimeout/requestTimeout) destroying sockets; race with client cancel during slow async handlers; double handling of the same upgrade event by two listeners.
Related errors
- Socket has no handle - cannot upgrade
- Already upgraded
- Invalid Header: 'upgrade' header must contain 'websocket'
- Socket is not a TCP socket - only TCP connections can be upg
- Already closed
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/e2d57c933da2542a.
Report an issue: GitHub.