moeru-ai/airi · error · Error

Extension entrypoint id `${extension.id}` must match manifes

Error message

Extension entrypoint id `${extension.id}` must match manifest id `${options.manifest.id}`.

What it means

Thrown by ExtensionHost.startExtension() when the id on the loaded Extension object (from defineExtension) does not equal the id declared in the ExtensionManifestV1 passed to start/startExtension. The host treats the manifest id as the canonical package identity and refuses to start a mismatched entrypoint to prevent impersonation and stale-binding bugs.

Source

Thrown at packages/plugin-sdk/src/plugin-host/core.ts:240

  constructor(options: ExtensionHostOptions = {}) {
    this.loader = new FileSystemLoader()
    this.runtime = options.runtime ?? 'electron'
    this.permissionResolver = options.permissionResolver
    this.resources.setValue(protocolListProvidersEventName, [] as Array<{ name: string }>)
    this.markCapabilityReady(protocolListProvidersEventName, { source: 'plugin-host' })
    this.installContext = this.createInstallContext()

    for (const contribution of options.contributions ?? []) {
      this.installContribution(contribution)
    }
  }

  async startExtension(
    extension: Extension,
    options: { manifest: ExtensionManifestV1, cwd?: string, runtime?: PluginRuntime },
  ) {
    if (extension.id !== options.manifest.id) {
      throw new Error(`Extension entrypoint id \`${extension.id}\` must match manifest id \`${options.manifest.id}\`.`)
    }

    const sessionIdentity = this.extensionSessionService.nextSessionIdentity()
    const extensionIdentity = {
      id: extension.id,
      version: extension.version,
      sessionId: sessionIdentity.sessionId,
    }
    const persistedGrant = this.persistedPermissionGrants.get(extension.id)
    const resolvedGrant = await this.permissionResolver?.({
      identity: extensionIdentity,
      manifest: options.manifest,
      requested: options.manifest.permissions,
      persisted: persistedGrant,
    }) ?? options.manifest.permissions
    const permissionSnapshot = this.permissions.initialize(sessionIdentity.sessionId, options.manifest.permissions, {
      grant: resolvedGrant,
      persisted: this.permissionResolver ? undefined : persistedGrant,

View on GitHub (pinned to 27111382b4)

Solutions

  1. Align the id in defineExtension({ id: '...' }) with the `id` field in the extension manifest (manifest.json or equivalent).
  2. If renaming, update both the manifest id and the entrypoint defineExtension id in the same change.
  3. Add a lint/test step that asserts manifest.id === entrypoint exported id after loading.

Example fix

// manifest.json
{ "apiVersion": "v1", "kind": "manifest.extension.airi.moeru.ai", "id": "my-ext", ... }

// before — entrypoint mismatch
export default defineExtension({ id: 'old-ext', setup(ctx) { ... } })

// after
export default defineExtension({ id: 'my-ext', setup(ctx) { ... } })
Defensive patterns

Strategy: validation

Validate before calling

const extension = await loader.loadExtensionFor(manifest, options)
if (extension.id !== manifest.id) {
  throw new Error(`Entrypoint id '${extension.id}' does not match manifest id '${manifest.id}'. Fix the mismatch before starting.`)
}
await host.startExtension(extension, { manifest })

Type guard

function extensionIdMatchesManifest(extension: Extension, manifest: ExtensionManifestV1): boolean {
  return extension.id === manifest.id
}

Try / catch

try {
  await host.start(manifest)
} catch (error) {
  if (error instanceof Error && /must match manifest id/.test(error.message)) {
    // align defineExtension id with manifest id, then retry
  } else {
    throw error
  }
}

Prevention

When it happens

Trigger: Calling host.start(manifest) or host.startExtension(extension, { manifest }) where extension.id (set inside defineExtension({ id })) differs from manifest.id. Typically the entrypoint file was edited but the manifest was not, or two extensions share a single entrypoint with different ids.

Common situations: Renaming an extension: the manifest id was updated but the defineExtension call still uses the old id (or vice versa). Copying an extension folder and forgetting to change one of the two ids. Build tooling that generates the manifest from a different source than the entrypoint.

Related errors


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