{"record":{"id":"c35c06f2e4c6c62d","repo":"moeru-ai/airi","slug":"cannot-grant-permissions-to-unknown-plugin-exte","errorCode":null,"errorMessage":"Cannot grant permissions to unknown plugin \"${extensionId}\".","messagePattern":"Cannot grant permissions to unknown plugin \"(.+?)\"\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/plugin-host/runtimes/shared/services/permissions.ts","lineNumber":339,"sourceCode":"    if (!existing) {\n      throw new Error(`Cannot declare permissions for unknown plugin \"${extensionId}\".`)\n    }\n\n    const requested = normalizeDeclaration(requestedDeclaration)\n    const snapshot: PermissionSnapshot = {\n      requested: mergePermissionDeclarations(existing.requested, requested),\n      granted: existing.granted,\n      revision: existing.revision + 1,\n    }\n\n    this.store.set(extensionId, snapshot)\n    return snapshot\n  }\n\n  grant(extensionId: string, grant: ModulePermissionGrant) {\n    const existing = this.store.get(extensionId)\n    if (!existing) {\n      throw new Error(`Cannot grant permissions to unknown plugin \"${extensionId}\".`)\n    }\n\n    const mergedGranted = mergePermissions(existing.granted, grant)\n    const snapshot: PermissionSnapshot = {\n      requested: existing.requested,\n      granted: intersectPermissions(existing.requested, mergedGranted),\n      revision: existing.revision + 1,\n    }\n    this.store.set(extensionId, snapshot)\n    return snapshot\n  }\n\n  get(extensionId: string) {\n    return this.store.get(extensionId)\n  }\n\n  isAllowed(extensionId: string, area: ModulePermissionArea, action: string, key: string) {\n    const snapshot = this.store.get(extensionId)","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/plugin-sdk/src/plugin-host/runtimes/shared/services/permissions.ts#L321-L357","documentation":"Thrown by `PermissionRegistry.grant()` when no permission snapshot exists for the given `extensionId`. Granting is an incremental operation that intersects a new grant with an existing snapshot's requested ceiling, so the extension must already have been initialized via `initialize()`. The store miss is checked before any grant merging or intersection logic runs.","triggerScenarios":"Calling `permissions.grant(extensionId, grant)` before `permissions.initialize(extensionId, ...)` has created a snapshot for that extension. Also caused by a typo or scope mismatch in the extension id, or by granting after the store was cleared (reload/test) without re-initializing.","commonSituations":"Permission-prompt flow that grants in response to a user action before the extension's permission record was seeded; host code that reuses a stale extension id after a plugin reload into a fresh store; tests that call grant directly.","solutions":["Ensure `permissions.initialize(extensionId, requestedDeclaration)` runs before any `grant()` for the same extension id.","Check `permissions.get(extensionId)` before granting, and initialize if absent (the grant will then be intersected with the requested ceiling).","Confirm the extension id string matches exactly what was used at registration.","On store reset/reload, replay the initialize step before granting."],"exampleFix":"// before\npermissions.grant(extensionId, grant)\n\n// after\nif (!permissions.get(extensionId)) {\n  permissions.initialize(extensionId, requestedCeiling)\n}\npermissions.grant(extensionId, grant)","handlingStrategy":"validation","validationCode":"if (!permissions.get(extensionId)) {\n  permissions.initialize(extensionId, requestedCeiling)\n}\npermissions.grant(extensionId, grant)","typeGuard":"function isPermissionInitialized(extensionId: string): boolean {\n  return permissions.get(extensionId) !== undefined\n}","tryCatchPattern":"try {\n  permissions.grant(extensionId, grant)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Cannot grant permissions to unknown plugin')) {\n    permissions.initialize(extensionId, requestedCeiling)\n    permissions.grant(extensionId, grant)\n    return\n  }\n  throw e\n}","preventionTips":["Initialize each extension's permission snapshot before granting in response to user prompts.","Use identical extension id strings across initialize, declare, and grant call sites.","Guard grant calls with permissions.get(extensionId) in async flows (e.g. prompt callbacks).","After reload/test store resets, re-run initialize before granting."],"tags":["plugin-sdk","permissions","lifecycle","authorization"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}