denoland/deno · error · TypeError

Socket is not a TCP socket - only TCP connections can be upg

Error message

Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket

What it means

Third node-socket guard: the libuv handle must expose a takeStream function, which only TCP stream handles in Deno's node compat implement. Passing a socket whose underlying handle is not a TCP stream (e.g. a Unix domain socket, pipe, or TLS-wrapped handle that lacks takeStream) cannot be upgraded to a WebSocket this way.

Source

Thrown at ext/http/02_websocket.ts:165

    // 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 detach
        // the stream from libuv.
        await new Promise((resolve, reject) => {
          nodeSocket.write(responseHead, (err) => {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Serve WebSocket upgrades only on TCP listeners (server.listen({ port }) / listen({ host, port })), not unix sockets/pipes.
  2. Guard with typeof socket?._handle?.takeStream === 'function' before upgrading.
  3. For unix-socket IPC, use a different transport (Deno.UnixConn / WebSocket over TCP).

Example fix

// before
const server = createServer();
server.listen("/tmp/app.sock"); // unix socket
server.on("upgrade", (req, socket, head) => {
  Deno.upgradeWebSocket(req, { socket, head }); // handle has no takeStream
});

// after
const server = createServer();
server.listen(8080); // TCP
server.on("upgrade", (req, socket, head) => {
  if (typeof socket._handle?.takeStream !== "function") return;
  const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });
});
Defensive patterns

Strategy: type-guard

Validate before calling

server.on("upgrade", (req, socket, head) => {
  if (typeof (socket as any)?._handle?.takeStream !== "function") return socket.destroy();
  Deno.upgradeWebSocket(req, { socket, head });
});

Type guard

function isUpgradableTcpSocket(s: unknown): boolean { const h = (s as { _handle?: { takeStream?: unknown } } | null)?._handle; return typeof h?.takeStream === "function"; }

Try / catch

try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes("not a TCP socket")) { socket.destroy(); return; } throw e; }

Prevention

When it happens

Trigger: Calling the upgrade path with a Unix socket or named-pipe connection (server listening on a path); sockets from abstractions whose _handle is a custom object without takeStream; passing a TLS net.Socket whose handle type differs; interop with non-node runtimes that emulate the socket shape only partially.

Common situations: Servers listening on unix domain sockets for sidecar/IPC being reused for WS; test doubles mocking _handle as {}; adapters bridging other socket types into node-compatible shapes; internal-only listeners accidentally exposed to upgrade requests.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/5f32e409841a89b4. Report an issue: GitHub.