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
- Check liveness first: if (!host.getBinding(moduleId)) return — skip or re-announce to obtain a fresh moduleId.
- After host.reload(), re-discover ids via host.listBindings({ ownerSessionId }) instead of reusing pre-reload moduleIds.
- Verify the moduleId string at the call site (log it) to rule out typos or an unset variable.
- 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
- Refetch moduleIds from host.listBindings() after every reload; never cache them across session boundaries.
- Ignore session events once teardown has started so late transitions hit no dead binding.
- Log moduleId at transition call sites to catch typos early.
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
- Extension module ` ` is already registered for session .
- gameletKit requires a host gamelet orchestration runtime.
- Module ` ` was not found.
- Unable to reload missing extension session
- Unknown extension session
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)