Skip to content

HTTP/2 websocket client.upgrade() hangs if the stream closes before response headers #5064

@trivikr

Description

@trivikr

Bug Description

When Client#upgrade() is used over HTTP/2 for a websocket extended CONNECT, the returned promise/callback can hang indefinitely if the peer closes the stream before sending response headers.

Reproducible By

  1. Start an HTTP/2 server.
  2. Accept the websocket extended CONNECT stream.
  3. Close the stream before calling stream.respond().
  4. Call client.upgrade({ path: '/', protocol: 'websocket' }) with allowH2: true.
  5. Observe that the returned promise never settles.
import { once } from "node:events";
import { createSecureServer } from "node:http2";
import { generate } from "@metcoder95/https-pem";
import { Client } from "undici";

const server = createSecureServer({
  ...(await generate({ opts: { keySize: 2048 } })),
  settings: { enableConnectProtocol: true },
});

server.on("stream", (stream) => {
  // No response headers are sent before the stream is closed.
  stream.end();
  // `stream.close()` or another premature close path appears to trigger the same hang.
});

await once(server.listen(0), "listening");

const client = new Client(`https://localhost:${server.address().port}`, {
  allowH2: true,
  connect: {
    rejectUnauthorized: false,
  },
});

await client.upgrade({ path: "/", protocol: "websocket" });
console.log("unreachable: promise stays pending");

Expected Behavior

client.upgrade() should always settle. If the HTTP/2 stream closes before response headers are received, it should reject with a stream/protocol error instead of hanging indefinitely.

Logs & Screenshots

No error is surfaced to the caller in this case. The promise remains pending until the caller provides its own timeout or abort signal.

Environment

macOS 26.4.1
Node v24.14.1
undici v8.1.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions