{"id":"b2c0391291269a7f","repo":"vitejs/vite","slug":"invoke-was-called-before-connect","errorCode":null,"errorMessage":"invoke was called before connect","messagePattern":"invoke was called before connect","errorType":"exception","errorClass":"SendBeforeConnectError","httpStatus":null,"severity":"error","filePath":"packages/vite/src/shared/moduleRunnerTransport.ts","lineNumber":250,"sourceCode":"      : {}),\n    async send(data) {\n      if (!invokeableTransport.send) return\n\n      if (!isConnected) {\n        if (connectingPromise) {\n          await connectingPromise\n        } else {\n          throw new SendBeforeConnectError('send was called before connect')\n        }\n      }\n      await invokeableTransport.send(data)\n    },\n    async invoke(name, data) {\n      if (!isConnected) {\n        if (connectingPromise) {\n          await connectingPromise\n        } else {\n          throw new SendBeforeConnectError('invoke was called before connect')\n        }\n      }\n      return invokeableTransport.invoke(name, data)\n    },\n  }\n}\n\nexport class SendBeforeConnectError extends Error {\n  constructor(message: string) {\n    super(message)\n    this.name = 'SendBeforeConnectError'\n  }\n}\n\nexport const createWebSocketModuleRunnerTransport = (options: {\n  // eslint-disable-next-line n/no-unsupported-features/node-builtins\n  createConnection: () => WebSocket\n  pingInterval?: number","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/vitejs/vite/blob/89620f09afcfef6b35e7bb8660132ab5b4d0cd3b/packages/vite/src/shared/moduleRunnerTransport.ts#L232-L268","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure transport.connect() is awaited before the module runner imports anything that triggers invoke.","Let the ModuleRunner own connection lifecycle rather than invoking manually before connect.","Make the underlying channel open synchronously where possible, or await its readiness in connect()."],"exampleFix":"// before — invoking before connect resolves\nconst transport = normalizeModuleRunnerTransport(asyncTransport)\nconst result = await transport.invoke('fetchModule', ['id']) // throws\n\n// after — connect, then invoke\nconst transport = normalizeModuleRunnerTransport(asyncTransport)\nawait transport.connect()\nconst result = await transport.invoke('fetchModule', ['id'])","handlingStrategy":"validation","validationCode":"// Ensure connect resolves before invoking RPC\nawait transport.connect()\nconst result = await transport.invoke('fetchModule', ['id'])","typeGuard":null,"tryCatchPattern":"import { SendBeforeConnectError } from 'vite/module-runner'\n\ntry {\n  await transport.invoke(name, data)\n} catch (e) {\n  if (e instanceof SendBeforeConnectError) {\n    await transport.connect()\n    return await transport.invoke(name, data)\n  }\n  throw e\n}","preventionTips":["Await connect() before any invoke call.","Defer module imports until after the transport connection is established.","Await the channel's open/readiness inside connect() so isConnected is accurate."],"tags":["module-runner","transport","connection","rpc"],"analyzedSha":"89620f09afcfef6b35e7bb8660132ab5b4d0cd3b","analyzedAt":"2026-08-03T19:28:02.920Z","schemaVersion":2}