{"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":"validation","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/677329427f32468c74b17f3ec47eeca4e05bec65/packages/plugin-sdk/src/plugin-host/core.ts#L222-L258","documentation":"plugin-host's startExtension (packages/plugin-sdk/src/plugin-host/core.ts) requires that the Extension object your entrypoint exported has an id identical to the id declared in the ExtensionManifestV1 passed alongside it. The manifest id is the trust anchor for permission grants and session identity, so a mismatch between 'what the code claims to be' and 'what the manifest declares' is rejected outright. It throws before any session identity, permission resolution, or module registration happens.","triggerScenarios":"Loading a manifest.json whose id field was edited (e.g. renamed package) while the entrypoint still exports the old id; copying an existing extension as a template and changing only one of the two ids; build tooling that rewrites manifest ids (scoped names, versioned ids) without regenerating the entrypoint; publishing under a new name but keeping the source constant.","commonSituations":"Forking/templating extensions; monorepo codegen that mangles ids; manifest produced by a bundler with different normalization (slashes, @scope) than the runtime string; version bumps that accidentally touch the id.","solutions":["Make the two ids byte-identical: open the manifest, copy its id field, and use it in the exported extension object (or derive the export from a shared constant).","Better: have the entrypoint import/read the manifest id (or generate both from one source of truth) so they cannot drift.","If you renamed the extension intentionally, update both the manifest id and the exported extension id (plus any persisted permission grants keyed by the old id).","Add a unit test that loads the built manifest and asserts manifest.id === extension.id."],"exampleFix":"// before\n// manifest.json: { \"id\": \"com.acme.weather\", ... }\nexport const extension = defineExtension({ id: 'com.acme.wheather' }) // typo: mismatch\n\n// after\nimport manifest from './manifest.json'\nexport const extension = defineExtension({ id: manifest.id })","handlingStrategy":"validation","validationCode":"import manifest from './manifest.json' with { type: 'json' }\nif (extension.id !== manifest.id) {\n  throw new Error(`id drift: entrypoint='${extension.id}' manifest='${manifest.id}'`)\n}\nawait host.startExtension(extension, { manifest })","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive the exported extension id from the manifest (single source of truth) instead of duplicating the literal.","Add a CI/unit assertion that loads the built manifest and compares it against the exported extension id.","When renaming or forking an extension, update the manifest id, the entrypoint id, and any persisted grants keyed by the old id in one change."],"tags":["plugin-sdk","manifest","extension","identity","validation"],"backgroundTag":"manifest-id-mismatch","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}