{"record":{"id":"5c15ed8c941e53e2","repo":"vitejs/vite","slug":"module-runner-hmr-client-was-closed","errorCode":null,"errorMessage":"[module runner] HMR client was closed.","messagePattern":"\\[module runner\\] HMR client was closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vite/src/module-runner/runner.ts","lineNumber":392,"sourceCode":"    // disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710\n    const href = posixPathToFileHref(modulePath)\n    const meta = await createImportMeta(modulePath)\n    const exports = Object.create(null)\n    Object.defineProperty(exports, Symbol.toStringTag, {\n      value: 'Module',\n      enumerable: false,\n      configurable: false,\n    })\n\n    mod.exports = exports\n\n    let hotContext: ViteHotContext | undefined\n    if (this.hmrClient) {\n      Object.defineProperty(meta, 'hot', {\n        enumerable: true,\n        get: () => {\n          if (!this.hmrClient) {\n            throw new Error(`[module runner] HMR client was closed.`)\n          }\n          this.debug?.('[module runner] creating hmr context for', mod.url)\n          hotContext ||= new HMRContext(this.hmrClient, mod.url)\n          return hotContext\n        },\n        set: (value) => {\n          hotContext = value\n        },\n      })\n    }\n\n    const context: ModuleRunnerContext = {\n      [ssrImportKey]: request,\n      [ssrDynamicImportKey]: dynamicRequest,\n      [ssrModuleExportsKey]: exports,\n      [ssrExportAllKey]: (obj: any) => exportAll(exports, obj),\n      [ssrExportNameKey]: (name, getter) =>\n        Object.defineProperty(exports, name, {","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/vitejs/vite/blob/b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab/packages/vite/src/module-runner/runner.ts#L374-L410","documentation":"The runner installs a getter on `import.meta.hot` that throws if `this.hmrClient` is undefined. `hmrClient` is set to undefined by `clearCache()` and by `close()`, so any access to `import.meta.hot` after the HMR client is torn down triggers this error.","triggerScenarios":"User module code reading `import.meta.hot` (e.g. `if (import.meta.hot) { import.meta.hot.accept(...) }`) executed after `runner.close()` or `runner.clearCache()` while the HMR client is gone. Note: because the getter always exists on the meta object, `if (import.meta.hot)` truthiness checks alone do not protect against it.","commonSituations":"Teardown ordering: closing the runner while a module that registers HMR callbacks is still being evaluated/imported. Tests that clear the cache mid-import. SSR code that conditionally uses `import.meta.hot`.","solutions":["Await all in-flight imports before calling `runner.close()` or `runner.clearCache()`.","Guard HMR usage by checking `runner.isClosed()` from outside the module, or wrap `import.meta.hot` access in try/catch.","Run HMR-dependent modules only while the HMR client is known to be alive."],"exampleFix":"// before - inside a module\nif (import.meta.hot) {\n  import.meta.hot.accept('/src/dep', () => reload())\n}\n\n// after - guard against closed runner via try/catch\ntry {\n  import.meta.hot?.accept('/src/dep', () => reload())\n} catch { /* runner closing/closed */ }","handlingStrategy":"try-catch","validationCode":"// Ensure no imports are in flight when tearing down\nasync function safeClose(runner: { import: (u: string)=>Promise<unknown>; close: ()=>Promise<void> }, inflight: Promise<unknown>[]) {\n  await Promise.allSettled(inflight)\n  await runner.close()\n}","typeGuard":"function hmrClientAlive(runner: any): boolean {\n  // hmrClient is private; infer via option state tracked externally\n  return !runner.isClosed()\n}","tryCatchPattern":"try {\n  if (import.meta.hot) import.meta.hot.accept('/dep', cb)\n} catch (e) {\n  if (e instanceof Error && /HMR client was closed/.test(e.message)) {\n    // runner is shutting down - skip HMR registration\n  } else throw e\n}","preventionTips":["Close the runner only after all imports have settled.","Don't rely solely on `if (import.meta.hot)` - the getter throws once closed; wrap in try/catch."],"tags":["module-runner","hmr","lifecycle","import-meta-hot"],"backgroundTag":null,"analyzedSha":"b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab","analyzedAt":"2026-08-11T11:49:19.515Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}