vitejs/vite · error · SendBeforeConnectError

invoke was called before connect

Error message

invoke was called before connect

What it means

Symmetric to the send-before-connect guard: the normalized transport's invoke() also requires isConnected to be true. If invoke is called before connect() has completed (and no connect is pending), Vite throws SendBeforeConnectError('invoke was called before connect') because the RPC layer has no channel to carry the request/response.

Source

Thrown at packages/vite/src/shared/moduleRunnerTransport.ts:250

      : {}),
    async send(data) {
      if (!invokeableTransport.send) return

      if (!isConnected) {
        if (connectingPromise) {
          await connectingPromise
        } else {
          throw new SendBeforeConnectError('send was called before connect')
        }
      }
      await invokeableTransport.send(data)
    },
    async invoke(name, data) {
      if (!isConnected) {
        if (connectingPromise) {
          await connectingPromise
        } else {
          throw new SendBeforeConnectError('invoke was called before connect')
        }
      }
      return invokeableTransport.invoke(name, data)
    },
  }
}

export class SendBeforeConnectError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'SendBeforeConnectError'
  }
}

export const createWebSocketModuleRunnerTransport = (options: {
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
  createConnection: () => WebSocket
  pingInterval?: number

View on GitHub (pinned to 89620f09af)

Solutions

  1. Ensure transport.connect() is awaited before the module runner imports anything that triggers invoke.
  2. Let the ModuleRunner own connection lifecycle rather than invoking manually before connect.
  3. Make the underlying channel open synchronously where possible, or await its readiness in connect().

Example fix

// before — invoking before connect resolves
const transport = normalizeModuleRunnerTransport(asyncTransport)
const result = await transport.invoke('fetchModule', ['id']) // throws

// after — connect, then invoke
const transport = normalizeModuleRunnerTransport(asyncTransport)
await transport.connect()
const result = await transport.invoke('fetchModule', ['id'])
Defensive patterns

Strategy: validation

Validate before calling

// Ensure connect resolves before invoking RPC
await transport.connect()
const result = await transport.invoke('fetchModule', ['id'])

Try / catch

import { SendBeforeConnectError } from 'vite/module-runner'

try {
  await transport.invoke(name, data)
} catch (e) {
  if (e instanceof SendBeforeConnectError) {
    await transport.connect()
    return await transport.invoke(name, data)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling transport.invoke(name, data) on a normalized module-runner transport before connect() resolves. This happens when the module runner issues an RPC (e.g. fetchModule) during construction or before the connection is awaited.

Common situations: A ModuleRunner constructed with a transport whose connect is async, and the runner immediately imports a module that triggers an invoke. Custom integration code that calls invoke at startup. Race between environment init and the transport's WebSocket open.

Related errors


AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03). Data as JSON: /data/errors/b2c0391291269a7f.json. Report an issue: GitHub.