{"record":{"id":"6b3b55ee9a9e7c0f","repo":"moeru-ai/airi","slug":"module-moduleid-was-not-found","errorCode":null,"errorMessage":"Module `${moduleId}` was not found.","messagePattern":"Module `(.+?)` was not found\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/plugin-host/core.ts","lineNumber":583,"sourceCode":"    contribution.install(this.installContext)\n  }\n\n  private async cleanupExtensionSession(session: ExtensionSession) {\n    session.phase = 'stopped'\n\n    for (const module of this.modules.listByOwner(session.id)) {\n      this.modules.withdraw(session.id, session.extension.id, module.moduleId)\n      this.modules.unbind(session.id, session.extension.id, module.moduleId)\n    }\n    await this.cleanupExtensionSessionModules(session)\n    await session.subscriptions.dispose()\n    this.extensionSessionService.remove(session.id)\n  }\n\n  private getModuleOrThrow(moduleId: string) {\n    const module = this.modules.get(moduleId)\n    if (!module) {\n      throw new Error(`Module \\`${moduleId}\\` was not found.`)\n    }\n\n    return module\n  }\n\n  private assertKitAvailableForRuntime(kitId: string, runtime: PluginRuntime) {\n    const kit = this.kits.get(kitId)\n    if (!kit) {\n      throw new Error(`Kit \\`${kitId}\\` is not registered.`)\n    }\n\n    if (!kit.runtimes.includes(runtime)) {\n      throw new Error(`Kit \\`${kitId}\\` is not available for runtime \\`${runtime}\\`.`)\n    }\n\n    return kit\n  }\n","sourceCodeStart":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/packages/plugin-sdk/src/plugin-host/core.ts#L565-L601","documentation":"getModuleOrThrow (core.ts) looks a module up in the host's module registry by moduleId and throws this error when no such module exists. Modules enter the registry only via ctx.modules.register during an extension session and leave it on withdraw/unbind — stopExtension iterates owned modules and withdraws them all. So the error means either the id was never registered, or the owning extension's session already cleaned the module up.","triggerScenarios":"Passing a moduleId string that was never registered (typo, renamed module); calling a host API that takes (sessionId, moduleId) after the extension stopped and its modules were withdrawn; referencing a module registered under a different session; races where a call arrives after module disposal.","commonSituations":"Host-side orchestration code holding module ids across extension restarts; UI dropdowns listing modules from a stale snapshot; renaming a module id in the extension without updating host-side callers.","solutions":["Verify the module id against what the extension actually registers (ctx.modules.register({ id, ... })) — copy the exact string.","If the owning extension was stopped/reloaded, re-establish its session and wait for module registration before invoking module-scoped APIs.","List current modules from the registry (the host exposes listByOwner-style queries) instead of caching ids.","On the caller side, catch this and refresh module listings rather than retrying the same dead id."],"exampleFix":"// before\nawait host.callModule(sessionId, 'mocap-ui', input) // module id is 'ui.mocap'\n\n// after\nconst modules = await host.listModulesFor(sessionId)\nconst target = modules.find(m => m.moduleId.endsWith('mocap'))\nif (!target) throw new Error('mocap module not registered yet')\nawait host.callModule(sessionId, target.moduleId, input)","handlingStrategy":"validation","validationCode":"const modules = host.modules.listByOwner(sessionId) // or registry listing API\nconst target = modules.find(m => m.moduleId === moduleId)\nif (!target) {\n  // module withdrawn or not yet registered: refresh and wait instead of invoking\n}\nawait host.invokeModule(sessionId, moduleId, input)","typeGuard":null,"tryCatchPattern":"try {\n  await host.callModule(sessionId, moduleId, input)\n} catch (error) {\n  if (errorMessageFrom(error).includes('` was not found.')) {\n    // stale module id (extension restarted): re-resolve from the live registry\n    return refreshModulesAndRetry(sessionId, input)\n  }\n  throw error\n}","preventionTips":["Resolve module ids from the live registry at call time rather than caching them in UI state.","After stopping/reloading an extension, wait for its modules to re-register before invoking module-scoped APIs.","Use a shared constant for module ids shared between registration and callers so renames stay consistent."],"tags":["plugin-sdk","modules","registry","lookup","lifecycle"],"backgroundTag":"module-not-found","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}