moeru-ai/airi · error · Error

Module ` ` was not found.

Error message

Module `${moduleId}` was not found.

What it means

Thrown by KitApiBindingRegistry.transition (and the update/activate/degrade/withdraw helpers that delegate to it) when this.bindings.get(moduleId) returns nothing. Every lifecycle mutation requires a live binding record; the moduleId was either never announced, already unbound during session cleanup, or is a stale id held by a caller that outlived the binding.

Solutions

  1. Check liveness first: if (!host.getBinding(moduleId)) return — skip or re-announce to obtain a fresh moduleId.
  2. After host.reload(), re-discover ids via host.listBindings({ ownerSessionId }) instead of reusing pre-reload moduleIds.
  3. Verify the moduleId string at the call site (log it) to rule out typos or an unset variable.
  4. Sequence teardown before updates: stop reacting to a session's events once cleanup has begun so late transitions never fire.

Example fix

// before
registry.activate(owner, moduleId) // moduleId from before host.reload(...) -> throws

// after
if (!host.getBinding(moduleId)) {
  const [record] = host.listBindings({ ownerSessionId: newSession.id, kitId })
  moduleId = record!.moduleId
}
registry.activate(owner, moduleId)
Defensive patterns

Strategy: validation

Validate before calling

// Before activate/degrade/withdraw/update:
if (!host.getBinding(moduleId)) {
  const [record] = host.listBindings({ ownerSessionId: session.id })
  if (!record) return // binding already cleaned up; nothing to transition
  moduleId = record.moduleId
}

Type guard

const isLiveBinding = (host: ExtensionHost, moduleId: string): boolean => host.getBinding(moduleId) !== undefined

Try / catch

try {
  registry.activate(owner, moduleId)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Module `') && error.message.includes('was not found.')) {
    return // stale id after reload/cleanup — drop it instead of crashing
  }
  throw error
}

Prevention

When it happens

Trigger: Calling activate/update/degrade/withdraw with a moduleId from a previous session after reload (reload creates a new session identity and re-announces new ids); transitioning after cleanupExtensionSession unbound the module; typo or wrong variable passed as moduleId; announcing and transitioning through two different registry instances.

Common situations: Kit helpers caching module ids across reloads; UI code reacting to a stop/reload and then trying to degrade a now-removed module; concurrent teardown and update paths racing; tests reusing ids from a prior registry.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/b0fd6b5e5d3a38f1. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugin-sdk/src/plugin-host/runtimes/shared/services/kit-api-bindings.ts:419

   * Use when:
   * - A caller needs a custom lifecycle transition beyond the convenience helpers
   *
   * Expects:
   * - `owner` matches the stored binding owner
   * - `state`, when provided, is legal from the current lifecycle state
   *
   * Returns:
   * - The next canonical binding record written back into the registry
   */
  transition(
    owner: BindingOwnerIdentity,
    moduleId: string,
    state?: BindingState,
    patch: BindingUpdatePatch<C> = {},
  ) {
    const current = this.bindings.get(moduleId)
    if (!current) {
      throw new Error(`Module \`${moduleId}\` was not found.`)
    }

    if (
      current.ownerSessionId !== owner.ownerSessionId
      || current.ownerExtensionId !== owner.ownerExtensionId
    ) {
      throw createOwnershipError(
        moduleId,
        {
          ownerSessionId: current.ownerSessionId,
          ownerExtensionId: current.ownerExtensionId,
        },
        owner,
      )
    }

    const nextState = state ?? current.state
    if (!allowedBindingTransitions[current.state].includes(nextState)) {

View on GitHub (pinned to 677329427f)