moeru-ai/airi · error · Error
Extension module `${input.id}` is already registered for ses
Error message
Extension module `${input.id}` is already registered for session ${session.id}. What it means
Thrown inside the modules.register callback of ExtensionSetupContext when ctx.modules.register({ id }) is called twice with the same id within a single extension session. Each session owns a Map of modules keyed by id, and duplicate registration would overwrite the prior module's subscriptions and permissions.
Source
Thrown at packages/plugin-sdk/src/plugin-host/core.ts:288
modules: new Map(),
permissions: {
requested: permissionSnapshot.requested,
granted: permissionSnapshot.granted,
revision: permissionSnapshot.revision,
},
subscriptions,
}
this.extensionSessionService.register(session)
const ctx: ExtensionSetupContext = {
extension: session.extension,
kits: this.createExtensionKitRegistry(session),
subscriptions,
modules: {
register: async (input: RegisterExtensionModuleInput) => {
if (session.modules.has(input.id)) {
throw new Error(`Extension module \`${input.id}\` is already registered for session ${session.id}.`)
}
const moduleSubscriptions = new DisposableStore()
const permissions = this.permissions.intersectGrant(
session.permissions.granted,
input.permissions ?? session.permissions.granted,
)
const module: ExtensionModuleContext = {
id: input.id,
identity: {
id: input.id,
extension: session.extension,
labels: input.labels,
},
permissions,
kits: this.createModuleKitRegistry(session, moduleSubscriptions, input.id),
subscriptions: moduleSubscriptions,
dispose: async () => {View on GitHub (pinned to 27111382b4)
Solutions
- Use unique module ids for each ctx.modules.register call; derive ids from contribution keys if needed.
- Guard with session state: track which ids have been registered and skip or dispose-then-register if a repeat is intentional.
- If re-registration is expected, dispose the existing module first (the module context exposes a dispose method).
Example fix
// before
for (const c of contributions) {
ctx.modules.register({ id: 'widget', ... }) // duplicate 'widget' on second iteration
}
// after
for (const c of contributions) {
ctx.modules.register({ id: `widget-${c.key}`, ... })
} Defensive patterns
Strategy: validation
Validate before calling
// Inside setup(ctx):
if (sessionTrackedModuleIds.has(input.id)) {
throw new Error(`Module '${input.id}' already registered; choose a unique id.`)
}
sessionTrackedModuleIds.add(input.id)
await ctx.modules.register(input) Type guard
function isModuleIdUnique(alreadyRegistered: Set<string>, id: string): boolean {
return !alreadyRegistered.has(id)
} Try / catch
try {
await ctx.modules.register(input)
} catch (error) {
if (error instanceof Error && /already registered for session/.test(error.message)) {
// dispose existing module first, then re-register if intentional
} else {
throw error
}
} Prevention
- Derive module ids from stable contribution keys so duplicates surface immediately.
- Track registered ids in a Set during setup and assert uniqueness before calling register.
- If re-registration is intended, dispose the prior module context first.
When it happens
Trigger: Calling ctx.modules.register({ id: 'm1' }) more than once during a single extension.setup(ctx) invocation, or calling it again in a reload that reuses state incorrectly. The check is session-scoped: session.modules.has(input.id).
Common situations: A setup function loops over contributions and registers a module per contribution but two contributions share the same id. Or a developer calls register conditionally in two branches that both execute. Reloading an extension that did not fully dispose its prior modules can also surface this.
Related errors
- Identifier "{name}" is used more than once — names need to b
- Auth request failed (${error.status ?? 'unknown'})
- signOut failed
- Failed to retweet: ${errorToMessage(error)}
- Failed to fetch profile for @${username}
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/9e3c457cc83982a1.
Report an issue: GitHub.