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

  1. Check existence first: if (!host.getSession(sessionId)) { session = await host.start(manifest) } else { session = await host.reload(sessionId) }.
  2. List live sessions via host.listSessions() to discover the current ids instead of trusting a cached one.
  3. Guard against double reloads (e.g. an in-flight promise flag) so the second call cannot run after the first cleaned the session up.
  4. 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

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


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)