{"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/677329427f32468c74b17f3ec47eeca4e05bec65/packages/plugin-sdk/src/plugin-host/runtimes/shared/services/permissions.ts#L321-L357","documentation":"Thrown by PermissionService.grant when store.get(extensionId) is unset. Grants can only intersect with an existing snapshot (requested ceiling plus granted set); with no initialize call there is nothing to intersect with, so granting to an unknown plugin id is rejected. Same lifecycle rule as declare: initialize during session start must precede any grant.","triggerScenarios":"Calling grant('ext-1', {...}) from a permissions UI before the extension session was started; granting after a host restart wiped the in-memory store while the UI still had the old extension list; extensionId mismatch between the UI row and the initialized id; granting during teardown after the snapshot was cleared.","commonSituations":"Permission-management UIs operating on installed-but-not-started extensions; dev flows that recreate the host per request; id drift between the installer registry and the session service.","solutions":["Start/initialize the extension (host.start populates the snapshot) before enabling grant actions for it.","Guard in the UI layer: disable 'Grant' until permissions.get(extensionId) returns a snapshot.","Confirm the extensionId matches session.extension.id exactly.","After host recreation, re-initialize permission state (optionally passing persisted grants via initialize options) instead of granting into the void."],"exampleFix":"// before\nfunction onGrantClick(extensionId: string, grant: ModulePermissionGrant) {\n  svc.grant(extensionId, grant) // throws when not initialized\n}\n\n// after\nfunction onGrantClick(extensionId: string, grant: ModulePermissionGrant) {\n  if (!svc.get(extensionId)) {\n    svc.initialize(extensionId, {})\n  }\n  svc.grant(extensionId, grant)\n}","handlingStrategy":"validation","validationCode":"// Before granting from a UI:\nif (!permissions.get(extensionId)) {\n  throw new Error(`Start extension '${extensionId}' before granting permissions`)\n}\nconst snapshot = permissions.grant(extensionId, grant)","typeGuard":"const canGrant = (svc: PermissionService, extensionId: string): boolean => svc.get(extensionId) !== undefined","tryCatchPattern":"try {\n  svc.grant(extensionId, grant)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('unknown plugin')) {\n    // session not started yet: queue the grant and apply it after initialize\n    pendingGrants.set(extensionId, grant)\n    return\n  }\n  throw error\n}","preventionTips":["Disable grant controls for extensions whose permission snapshot is absent.","On host restart, re-initialize permission state (pass persisted grants through initialize options) before granting.","Re-apply queued grants right after host.start completes."],"tags":["plugin-sdk","permissions","grant","initialization"],"backgroundTag":"uninitialized-state-access","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"}