moeru-ai/airi · error · Error
Unknown extension session
Error message
Unknown extension session: ${sessionId} What it means
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.
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).
Example fix
// before
const session = await host.startExtension(ext, { manifest })
// ... later, after host.stopExtension(session.id)
await host.useModule(oldSessionId, moduleId) // throws: session removed
// after
await host.stopExtension(session.id)
const fresh = await host.startExtension(ext, { manifest })
await host.useModule(fresh.id, moduleId) Defensive patterns
Strategy: validation
Validate before calling
// before using a cached sessionId, confirm the session is still live
const sessions = host.listSessions?.() ?? []
if (!sessions.some(s => s.id === cachedSessionId)) {
cachedSession = await host.startExtension(extension, { manifest }) // re-establish
} Try / catch
try {
await host.withSession(sessionId, op)
} catch (error) {
if (errorMessageFrom(error).startsWith('Unknown extension session:')) {
// extension was stopped/reloaded: drop the cached handle and re-acquire
sessionId = undefined
return
}
throw error
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Extension module ` ` is already registered for session .
- Unable to reload missing extension session
- Chat session was removed before send completed
- Extension entrypoint id
- gameletKit requires a host gamelet orchestration runtime.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-08-18).
Data as JSON: /api/errors/46b847f9a422a871.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugin-sdk/src/plugin-host/core.ts:537
const grant = moduleId
? session.modules.get(moduleId)?.permissions
: session.permissions.granted
if (grant && this.permissions.grantAllows(grant, input.area, input.action, input.key)) {
return
}
throw new PermissionDeniedError({
area: input.area,
action: input.action,
key: input.key,
})
}
private getExtensionSessionOrThrow(sessionId: string) {
const session = this.extensionSessionService.get(sessionId)
if (!session) {
throw new Error(`Unknown extension session: ${sessionId}`)
}
return session
}
private createInstallContext(): ExtensionHostInstallContext {
return {
registerKit: kit => this.registerKit(kit),
unregisterKit: kitId => this.unregisterKit(kitId),
setResourceResolver: (key, resolver) => this.setResourceResolver(key, resolver),
setResourceValue: (key, value) => this.setResourceValue(key, value),
announceCapability: (key, metadata) => {
this.announceCapability(key, metadata)
},
markCapabilityReady: (key, metadata) => {
this.markCapabilityReady(key, metadata)
},
markCapabilityDegraded: (key, metadata) => {View on GitHub (pinned to 438a067dde)