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

  1. Use unique module ids for each ctx.modules.register call; derive ids from contribution keys if needed.
  2. Guard with session state: track which ids have been registered and skip or dispose-then-register if a repeat is intentional.
  3. 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

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


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/9e3c457cc83982a1. Report an issue: GitHub.