{"record":{"id":"399feea9f0949b4f","repo":"moeru-ai/airi","slug":"extension-entrypoint-id-extension-id-must-mat","errorCode":null,"errorMessage":"Extension entrypoint id `${extension.id}` must match manifest id `${options.manifest.id}`.","messagePattern":"Extension entrypoint id `(.+?)` must match manifest id `(.+?)`\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/plugin-host/core.ts","lineNumber":240,"sourceCode":"  constructor(options: ExtensionHostOptions = {}) {\n    this.loader = new FileSystemLoader()\n    this.runtime = options.runtime ?? 'electron'\n    this.permissionResolver = options.permissionResolver\n    this.resources.setValue(protocolListProvidersEventName, [] as Array<{ name: string }>)\n    this.markCapabilityReady(protocolListProvidersEventName, { source: 'plugin-host' })\n    this.installContext = this.createInstallContext()\n\n    for (const contribution of options.contributions ?? []) {\n      this.installContribution(contribution)\n    }\n  }\n\n  async startExtension(\n    extension: Extension,\n    options: { manifest: ExtensionManifestV1, cwd?: string, runtime?: PluginRuntime },\n  ) {\n    if (extension.id !== options.manifest.id) {\n      throw new Error(`Extension entrypoint id \\`${extension.id}\\` must match manifest id \\`${options.manifest.id}\\`.`)\n    }\n\n    const sessionIdentity = this.extensionSessionService.nextSessionIdentity()\n    const extensionIdentity = {\n      id: extension.id,\n      version: extension.version,\n      sessionId: sessionIdentity.sessionId,\n    }\n    const persistedGrant = this.persistedPermissionGrants.get(extension.id)\n    const resolvedGrant = await this.permissionResolver?.({\n      identity: extensionIdentity,\n      manifest: options.manifest,\n      requested: options.manifest.permissions,\n      persisted: persistedGrant,\n    }) ?? options.manifest.permissions\n    const permissionSnapshot = this.permissions.initialize(sessionIdentity.sessionId, options.manifest.permissions, {\n      grant: resolvedGrant,\n      persisted: this.permissionResolver ? undefined : persistedGrant,","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/plugin-sdk/src/plugin-host/core.ts#L222-L258","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Align the id in defineExtension({ id: '...' }) with the `id` field in the extension manifest (manifest.json or equivalent).","If renaming, update both the manifest id and the entrypoint defineExtension id in the same change.","Add a lint/test step that asserts manifest.id === entrypoint exported id after loading."],"exampleFix":"// manifest.json\n{ \"apiVersion\": \"v1\", \"kind\": \"manifest.extension.airi.moeru.ai\", \"id\": \"my-ext\", ... }\n\n// before — entrypoint mismatch\nexport default defineExtension({ id: 'old-ext', setup(ctx) { ... } })\n\n// after\nexport default defineExtension({ id: 'my-ext', setup(ctx) { ... } })","handlingStrategy":"validation","validationCode":"const extension = await loader.loadExtensionFor(manifest, options)\nif (extension.id !== manifest.id) {\n  throw new Error(`Entrypoint id '${extension.id}' does not match manifest id '${manifest.id}'. Fix the mismatch before starting.`)\n}\nawait host.startExtension(extension, { manifest })","typeGuard":"function extensionIdMatchesManifest(extension: Extension, manifest: ExtensionManifestV1): boolean {\n  return extension.id === manifest.id\n}","tryCatchPattern":"try {\n  await host.start(manifest)\n} catch (error) {\n  if (error instanceof Error && /must match manifest id/.test(error.message)) {\n    // align defineExtension id with manifest id, then retry\n  } else {\n    throw error\n  }\n}","preventionTips":["Keep the manifest id and the defineExtension id in sync; treat them as the same canonical identity.","Add a pre-publish test that loads the entrypoint and asserts its exported id equals the manifest id.","When renaming an extension, update both sources in one commit."],"tags":["manifest","identity","contract-violation","extension-lifecycle"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}