moeru-ai/airi · error · Error
Extension entrypoint id
Error message
Extension entrypoint id `${extension.id}` must match manifest id `${options.manifest.id}`. What it means
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.
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.
Example fix
// before
// manifest.json: { "id": "com.acme.weather", ... }
export const extension = defineExtension({ id: 'com.acme.wheather' }) // typo: mismatch
// after
import manifest from './manifest.json'
export const extension = defineExtension({ id: manifest.id }) Defensive patterns
Strategy: validation
Validate before calling
import manifest from './manifest.json' with { type: 'json' }
if (extension.id !== manifest.id) {
throw new Error(`id drift: entrypoint='${extension.id}' manifest='${manifest.id}'`)
}
await host.startExtension(extension, { manifest }) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Extension entrypoint is required for runtime
- Extension manifest is invalid
- Extension module ` ` is already registered for session .
- Permission denied: .
- Tool input schema must be a JSON Schema object or a…
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/399feea9f0949b4f.
Report an issue: GitHub.
Appendix: 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 677329427f)