Skip to content

HTTP/2 successful CONNECT / WebSocket upgrade stall queued requests #5131

Description

@trivikr

Bug Description

HTTP/2 successful CONNECT / WebSocket upgrade stall queued requests

Reproducible By

import { createSecureServer } from 'node:http2'
import { once } from 'node:events'
import { setTimeout as sleep } from 'node:timers/promises'

import { generate } from '@metcoder95/https-pem'
import { Client } from 'undici'

let postReachedServer = false

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

server.on('stream', (stream, headers) => {
  if (headers[':method'] === 'CONNECT' && headers[':protocol'] === 'websocket') {
    stream.respond({ ':status': 200 })
    return
  }

  if (headers[':method'] === 'POST') {
    postReachedServer = true
    stream.resume()
    stream.respond({ ':status': 200 })
    stream.end('ok')
    return
  }

  stream.close()
})

server.listen(0)
await once(server, 'listening')

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

let upgraded

try {
  const upgrade = client.upgrade({
    path: '/',
    protocol: 'websocket'
  })

  const post = client.request({
    method: 'POST',
    path: '/',
    body: 'hello'
  })

  upgraded = await upgrade
  console.log('upgrade resolved')

  const result = await Promise.race([
    post.then(({ statusCode }) => ({ statusCode })),
    sleep(1000).then(() => null)
  ])

  if (result == null) {
    throw new Error(`POST stalled; reached server: ${postReachedServer}`)
  }

  console.log(`POST resolved with status ${result.statusCode}`)
} finally {
  upgraded?.socket.on('error', () => {})
  upgraded?.socket.end()
  await client.destroy()
  await new Promise(resolve => server.close(resolve))
}

Expected Behavior

After the upgrade request successfully completes from Undici’s dispatcher perspective, the client should resume dispatching queued work.

For the minimal repro, the output should be

upgrade resolved
POST resolved with status 200

Logs & Screenshots

The WebSocket upgrade succeeds, but the queued POST never reaches the server.

For the minimal repro, the output is

upgrade resolved
file:///Users/trivikram/workspace/test-repro/test.mjs:62
    throw new Error(`POST stalled; reached server: ${postReachedServer}`)
          ^

Error: POST stalled; reached server: false
    at file:///Users/trivikram/workspace/test-repro/test.mjs:62:11

Environment

macOS 26.4.1
Node v24.15.0
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