{"id":"f15a9abd2cfe6424","repo":"vitejs/vite","slug":"vite-module-runner-has-been-closed","errorCode":null,"errorMessage":"Vite module runner has been closed.","messagePattern":"Vite module runner has been closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vite/src/module-runner/runner.ts","lineNumber":273,"sourceCode":"              builtin,\n        )\n        this.isBuiltin = createIsBuiltin(builtins)\n        this.debug?.('[module runner] builtins loaded:', builtins)\n      } finally {\n        this.builtinsPromise = undefined\n      }\n    })()\n\n    return this.builtinsPromise\n  }\n\n  private async getModuleInformation(\n    url: string,\n    importer: string | undefined,\n    cachedModule: EvaluatedModuleNode | undefined,\n  ): Promise<EvaluatedModuleNode> {\n    if (this.closed) {\n      throw new Error(`Vite module runner has been closed.`)\n    }\n\n    await this.ensureBuiltins()\n\n    this.debug?.('[module runner] fetching', url)\n\n    const isCached = !!(typeof cachedModule === 'object' && cachedModule.meta)\n\n    const fetchedModule = // fast return for established externalized pattern\n      (\n        url.startsWith('data:') || this.isBuiltin?.(url)\n          ? { externalize: url, type: 'builtin' }\n          : await this.transport.invoke('fetchModule', [\n              url,\n              importer,\n              {\n                cached: isCached,\n                startOffset: this.evaluator.startOffset,","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/vitejs/vite/blob/89620f09afcfef6b35e7bb8660132ab5b4d0cd3b/packages/vite/src/module-runner/runner.ts#L255-L291","documentation":"Thrown in getModuleInformation when attempting to fetch a module after runner.close() has been called. The close() method sets this.closed = true, clears caches, removes HMR listeners, and disconnects the transport. Any subsequent import() call that triggers a module fetch hits this guard because the runner is no longer operational.","triggerScenarios":"Calling runner.import() or runner.import(url) after runner.close() has been awaited. Common in test teardown, SSR request handlers that close the runner prematurely, or when a long-running promise tries to import a module after the runner lifecycle has ended.","commonSituations":"In SSR or test environments, the runner is closed during shutdown but an in-flight async operation (e.g., a pending import, a lazy-loaded route handler) attempts to load a module after close. Also happens with incorrect lifecycle management where close() is called too early.","solutions":["Check runner.isClosed() before attempting to import: if (!runner.isClosed()) { await runner.import(url) }.","Ensure close() is only called after all pending imports have resolved — track in-flight operations.","If using the runner for SSR, create a new runner instance for each request lifecycle rather than reusing a closed one.","Restructure shutdown logic so close() is the last operation, called after all request handling is complete."],"exampleFix":"// before — import after close throws\nawait runner.close()\nawait runner.import('/src/app.ts') // throws\n// after — guard against closed state\nif (!runner.isClosed()) {\n  await runner.import('/src/app.ts')\n} else {\n  // recreate runner or handle gracefully\n}","handlingStrategy":"validation","validationCode":"// Guard imports against closed runner state\nasync function safeImport(runner, url) {\n  if (runner.isClosed()) {\n    throw new Error(`Cannot import ${url}: runner is closed. Create a new instance.`)\n  }\n  return runner.import(url)\n}","typeGuard":"function isRunnerActive(runner: ModuleRunner): boolean {\n  return !runner.isClosed()\n}","tryCatchPattern":"try {\n  await runner.import(url)\n} catch (e) {\n  if (e.message.includes('has been closed')) {\n    // recreate runner or skip\n    console.warn('Runner closed, skipping import')\n    return null\n  }\n  throw e\n}","preventionTips":["Always call runner.isClosed() before import() in long-lived async code.","Track in-flight import promises and await them all before calling close().","Use a wrapper that queues or rejects imports after close rather than letting them throw internally.","Structure SSR code so the runner lifecycle matches request lifecycle."],"tags":["module-runner","lifecycle","close","ssr"],"analyzedSha":"89620f09afcfef6b35e7bb8660132ab5b4d0cd3b","analyzedAt":"2026-08-03T19:28:02.920Z","schemaVersion":2}