{"record":{"id":"46b847f9a422a871","repo":"moeru-ai/airi","slug":"unknown-extension-session-sessionid","errorCode":null,"errorMessage":"Unknown extension session: ${sessionId}","messagePattern":"Unknown extension session: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/plugin-host/core.ts","lineNumber":537,"sourceCode":"    const grant = moduleId\n      ? session.modules.get(moduleId)?.permissions\n      : session.permissions.granted\n\n    if (grant && this.permissions.grantAllows(grant, input.area, input.action, input.key)) {\n      return\n    }\n\n    throw new PermissionDeniedError({\n      area: input.area,\n      action: input.action,\n      key: input.key,\n    })\n  }\n\n  private getExtensionSessionOrThrow(sessionId: string) {\n    const session = this.extensionSessionService.get(sessionId)\n    if (!session) {\n      throw new Error(`Unknown extension session: ${sessionId}`)\n    }\n\n    return session\n  }\n\n  private createInstallContext(): ExtensionHostInstallContext {\n    return {\n      registerKit: kit => this.registerKit(kit),\n      unregisterKit: kitId => this.unregisterKit(kitId),\n      setResourceResolver: (key, resolver) => this.setResourceResolver(key, resolver),\n      setResourceValue: (key, value) => this.setResourceValue(key, value),\n      announceCapability: (key, metadata) => {\n        this.announceCapability(key, metadata)\n      },\n      markCapabilityReady: (key, metadata) => {\n        this.markCapabilityReady(key, metadata)\n      },\n      markCapabilityDegraded: (key, metadata) => {","sourceCodeStart":519,"sourceCodeEnd":555,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/packages/plugin-sdk/src/plugin-host/core.ts#L519-L555","documentation":"getExtensionSessionOrThrow (core.ts) maps a sessionId to a live ExtensionSession via the extension session service and throws this error when the id is unknown. Sessions are created by startExtension and removed when the extension stops (stopExtension disposes subscriptions and removes the session), so this error means the caller referenced a session that never existed or that has already been torn down. Most host APIs that operate per-session resolve through this lookup first.","triggerScenarios":"Using a sessionId captured before a stopExtension/dispose (e.g. after hot-reload, extension disable, or app teardown); a typo or truncated id; concurrent stop racing an in-flight host call that carries the old session id; persisting session ids across app restarts and replaying them.","commonSituations":"Dev hot-reload restarting extensions while UI code holds old session handles; disable/re-enable flows where pending promises resolve after removal; distributed/multi-window callers caching session ids longer than their lifetime.","solutions":["Re-acquire the session: call startExtension again (or re-read the active sessions list) and use the fresh session id.","Treat this error as a signal to stop issuing calls with the stale handle — clear cached ids when an extension is stopped/disabled.","Serialize stop-then-call sequences so host calls complete before stopExtension removes the session.","Never persist session ids; they are per-run identities by design (session ids come from nextSessionIdentity)."],"exampleFix":"// before\nconst session = await host.startExtension(ext, { manifest })\n// ... later, after host.stopExtension(session.id)\nawait host.useModule(oldSessionId, moduleId) // throws: session removed\n\n// after\nawait host.stopExtension(session.id)\nconst fresh = await host.startExtension(ext, { manifest })\nawait host.useModule(fresh.id, moduleId)","handlingStrategy":"validation","validationCode":"// before using a cached sessionId, confirm the session is still live\nconst sessions = host.listSessions?.() ?? []\nif (!sessions.some(s => s.id === cachedSessionId)) {\n  cachedSession = await host.startExtension(extension, { manifest }) // re-establish\n}","typeGuard":null,"tryCatchPattern":"try {\n  await host.withSession(sessionId, op)\n} catch (error) {\n  if (errorMessageFrom(error).startsWith('Unknown extension session:')) {\n    // extension was stopped/reloaded: drop the cached handle and re-acquire\n    sessionId = undefined\n    return\n  }\n  throw error\n}","preventionTips":["Never persist session ids across restarts — they are per-run identities from nextSessionIdentity().","Invalidate cached session handles whenever an extension is stopped, disabled, or hot-reloaded.","Order host calls to complete before stopExtension/dispose so lookups never race session removal."],"tags":["plugin-sdk","session","lifecycle","stale-handle","extension"],"backgroundTag":"session-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"}