honojs/hono · error · Error

Failed to upgrade WebSocket

Error message

Failed to upgrade WebSocket

What it means

defineWebSocketHelper wraps runtime-specific WebSocket upgrade handlers. In the three-argument form (Context, events, options) it awaits the handler and, if the handler returns a falsy value (the upgrade did not succeed), throws this generic Error indicating the HTTP->WebSocket upgrade was rejected.

Source

Thrown at src/helper/websocket/index.ts:134

      | [createEvents: (c: Context) => WSEvents<T> | Promise<WSEvents<T>>, options?: U]
      | [c: Context, events: WSEvents<T>, options?: U]
  ) => {
    if (typeof args[0] === 'function') {
      const [createEvents, options] = args
      return async function upgradeWebSocket(c, next) {
        const events = await createEvents(c)
        const result = await handler(c, events, options as U)
        if (result) {
          return result
        }
        await next()
      }
    } else {
      const [c, events, options] = args as [c: Context, events: WSEvents<T>, options?: U]
      return (async () => {
        const upgraded = await handler(c, events, options as U)
        if (!upgraded) {
          throw new Error('Failed to upgrade WebSocket')
        }
        return upgraded
      })()
    }
  }) as UpgradeWebSocket<T, U>
}

View on GitHub (pinned to e2740d5a1b)

Solutions

  1. Check the runtime/adapter pairing: use createBunWebSocket on Bun.serve, @hono/node-server's WS helper on Node, and the Cloudflare/Deno helpers accordingly
  2. Ensure the app is the direct fetch handler of the runtime server so upgrade machinery is available
  3. Verify the client sends a real WebSocket upgrade request (Upgrade: websocket, Sec-WebSocket-Key headers) to the WS route
  4. In tests use the runtime's WS test utilities (e.g. @hono/node-server createAdaptorServer + ws client), not app.request()

Example fix

// before
import { createBunWebSocket } from 'hono/bun'
// but app served by @hono/node-server -> handler returns falsy -> throws

// after (Node)
import { createNodeWebSocket } from '@hono/node-server'
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket(app)
const server = serve({ fetch: app.fetch })
injectWebSocket(server)
Defensive patterns

Strategy: try-catch

Validate before calling

const isUpgradeRequest = (c: Context) =>
  c.req.header('upgrade')?.toLowerCase() === 'websocket' &&
  !!c.req.header('sec-websocket-key')

app.get('/ws', (c) => {
  if (!isUpgradeRequest(c)) return c.text('WebSocket endpoint', 426)
  return upgradeWebSocket(handler)(c)
})

Type guard

const isUpgradeRequest = (c: { req: { header(k: string): string | undefined } }): boolean =>
  c.req.header('upgrade')?.toLowerCase() === 'websocket'

Try / catch

try { return await upgradeWebSocket(handler)(c, events) } catch (e) { if (e instanceof Error && e.message === 'Failed to upgrade WebSocket') return c.text('upgrade failed', 500); throw e }

Prevention

When it happens

Trigger: A WebSocket route using upgradeWebSocket() where the underlying runtime handler returns falsy: Bun upgrade() failing (c.env.server missing — see Bun env error), wrong route/method, subprotocol/version mismatch, or handler returning undefined instead of a Response/context.

Common situations: Bun/Node adapter mismatch (using the wrong runtime's WS helper); app not mounted via the expected server (Bun.serve) so upgrade can't run; request not actually a WebSocket upgrade (missing Upgrade header); testing WS routes with plain app.request() without upgrade headers.

Related errors


AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28). Data as JSON: /api/errors/c00ecbe4d40fd059. Report an issue: GitHub.