moeru-ai/airi · error · Error
Unable to reload missing extension session
Error message
Unable to reload missing extension session: ${sessionId} What it means
Thrown by ExtensionHost.reload(sessionId) when extensionSessionService.get(sessionId) returns nothing. reload intentionally performs stop + fresh start and needs the previous session's manifest, cwd, and runtime to preserve intent, so it refuses to operate on an unknown id. The session may never have existed, or it was already stopped/cleaned up.
Solutions
- Check existence first: if (!host.getSession(sessionId)) { session = await host.start(manifest) } else { session = await host.reload(sessionId) }.
- List live sessions via host.listSessions() to discover the current ids instead of trusting a cached one.
- Guard against double reloads (e.g. an in-flight promise flag) so the second call cannot run after the first cleaned the session up.
- If the host was recreated (test/dev restart), re-start the extension from its manifest rather than reloading old ids.
Example fix
// before const session = await host.reload(sessionId) // throws when the session is gone // after const session = host.getSession(sessionId) ? await host.reload(sessionId) : await host.start(manifest)
Defensive patterns
Strategy: validation
Validate before calling
// Before reloading: const existing = host.getSession(sessionId) const next = existing ? await host.reload(sessionId) : await host.start(manifest)
Type guard
const canReload = (host: ExtensionHost, sessionId: string): boolean => host.getSession(sessionId) !== undefined
Try / catch
try {
await host.reload(sessionId)
} catch (error) {
if (error instanceof Error && error.message.startsWith('Unable to reload missing extension session')) {
// fall back to a fresh start from the persisted manifest
return host.start(manifest)
}
throw error
} Prevention
- Treat sessionId as ephemeral per host instance; persist the manifest/path for recovery instead.
- Serialize reload/stop operations for the same session (in-flight flag) so cleanup cannot race a second reload.
- Refresh UI state from host.listSessions() rather than caching ids.
When it happens
Trigger: Calling host.reload('sess-1') before host.start returned that session; reloading after host.stop(sessionId) already cleaned it up; two concurrent reloads where the first one's cleanup removes the session before the second runs; persisting a sessionId across host restarts (the in-memory session service is per host instance) and reloading it on a new host.
Common situations: Dev-tooling 'reload extension' buttons that keep a stale session id; retry logic that re-fires reload after the first succeeded; splitting start/stop and reload across processes or test setups where each test creates a fresh host; races between UI-triggered reload and automatic teardown.
Related errors
- Unknown extension session
- Chat session was removed before send completed
- Extension module ` ` is already registered for session .
- gameletKit requires a host gamelet orchestration runtime.
- Module ` ` was not found.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-08-18).
Data as JSON: /api/errors/1ebf540e380cdd91.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugin-sdk/src/plugin-host/core.ts:870
return await this.dependencies.waitFor(key, timeoutMs)
}
async stop(sessionId: string): Promise<ExtensionSession | undefined> {
const extensionSession = this.extensionSessionService.get(sessionId)
if (!extensionSession) {
return undefined
}
await this.cleanupExtensionSession(extensionSession)
return extensionSession
}
async reload(sessionId: string, options: ExtensionStartOptions = {}): Promise<ExtensionSession> {
// Reload preserves manifest/runtime intent, then performs stop + fresh start.
// This intentionally creates a new session identity for deterministic re-bootstrap.
const previousExtension = this.extensionSessionService.get(sessionId)
if (!previousExtension) {
throw new Error(`Unable to reload missing extension session: ${sessionId}`)
}
const manifest = previousExtension.manifest
await this.cleanupExtensionSession(previousExtension)
return this.start(manifest, {
...options,
cwd: options.cwd ?? previousExtension.cwd,
runtime: options.runtime ?? previousExtension.runtime,
})
}
}
View on GitHub (pinned to 438a067dde)