{"record":{"id":"9b638c155bccd3b7","repo":"moeru-ai/airi","slug":"permission-denied-details-area-details-actio","errorCode":null,"errorMessage":"Permission denied: ${details.area}.${details.action} \"${details.key}\"","messagePattern":"Permission denied: (.+?)\\.(.+?) \"(.+?)\"","errorType":"exception","errorClass":"PermissionDeniedError","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/plugin-host/core.ts","lineNumber":506,"sourceCode":"\n  private createModuleKitRegistry(session: ExtensionSession, subscriptions: DisposableStore, moduleId: string): ExtensionModuleContext['kits'] {\n    return this.createKitRegistry(session, subscriptions, moduleId)\n  }\n\n  private assertExtensionPermission(\n    session: ExtensionSession,\n    input: ExtensionHostPermissionRequest,\n    moduleId?: string,\n  ) {\n    const grant = moduleId\n      ? session.modules.get(moduleId)?.permissions\n      : session.permissions.granted\n\n    if (grant && this.permissions.grantAllows(grant, input.area, input.action, input.key)) {\n      return\n    }\n\n    throw new PermissionDeniedError({\n      area: input.area,\n      action: input.action,\n      key: input.key,\n    })\n  }\n\n  private getExtensionSessionOrThrow(sessionId: string) {\n    const session = this.extensionSessionService.get(sessionId)\n    if (!session) {\n      throw new Error(`Unknown extension session: ${sessionId}`)\n    }\n\n    return session\n  }\n\n  private createInstallContext(): ExtensionHostInstallContext {\n    return {\n      registerKit: kit => this.registerKit(kit),","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/plugin-sdk/src/plugin-host/core.ts#L488-L524","documentation":"Thrown as PermissionDeniedError by assertExtensionPermission() when neither the session/module permission grant allows the requested area.action.key triple. The error carries structured `details` (area, action, key) so callers can inspect which permission was missing. It is the host's enforcement point for apis/resources/capabilities/processors/pipelines access.","triggerScenarios":"An extension (or module) calls a host API that triggers assertExtensionPermission — e.g. announceBinding, activateBinding, updateBinding, withdrawBinding, bindExtensionKitModule — and the manifest/module permission grant does not include the required apis.invoke.<eventName> or resources.write.<kitBindingResourceKey> entry.","commonSituations":"Manifest permissions block was omitted or did not declare the needed api invoke key or resource write key. A module was registered with a narrower permission grant (via input.permissions intersected with the session grant) and tried an action outside that scope. permissionResolver returned a grant missing the required entry.","solutions":["Add the missing permission to the extension manifest under the correct area (apis/resources/capabilities) with the action and key shown in the error details.","If the error is module-scoped, widen the input.permissions passed to ctx.modules.register to include the required key.","Ensure permissionResolver (if configured) returns a grant that includes the needed entry, or remove the resolver so the manifest grant is used directly.","Catch PermissionDeniedError by name to present a user-facing permission prompt instead of crashing."],"exampleFix":"// before — manifest lacks the binding announce permission\n{ id: 'my-ext', permissions: { apis: [{ key: 'plugin:binding:activate', actions: ['invoke'] }] } }\n\n// after\n{ id: 'my-ext', permissions: {\n  apis: [\n    { key: 'plugin:binding:announce', actions: ['invoke'] },\n    { key: 'plugin:binding:activate', actions: ['invoke'] },\n  ],\n  resources: [{ key: 'kit:gamelet:binding', actions: ['write'] }],\n} }","handlingStrategy":"try-catch","validationCode":"// Before calling a binding API, ensure the manifest/module grant includes the required permission.\nfunction grantAllows(grant, area, action, key): boolean { /* inspect grant entries */ }\nif (!grantAllows(session.permissions.granted, 'apis', 'invoke', 'plugin:binding:announce')) {\n  throw new Error('Missing required permission: apis.invoke.plugin:binding:announce')\n}","typeGuard":"function isPermissionDeniedError(error: unknown): error is Error & { details: { area: string, action: string, key: string } } {\n  return error instanceof Error && error.name === 'PermissionDeniedError'\n}","tryCatchPattern":"try {\n  host.announceBinding(sessionId, input)\n} catch (error) {\n  if (error instanceof Error && error.name === 'PermissionDeniedError') {\n    // prompt the user to grant the missing permission, then retry\n    const { area, action, key } = (error as any).details\n    await requestPermissionFromUser({ area, action, key })\n  } else {\n    throw error\n  }\n}","preventionTips":["Declare every api/resource/capability key the extension will use in the manifest permissions block.","When narrowing module permissions via input.permissions, include all keys the module will exercise.","If using a permissionResolver, ensure it returns a grant covering the declared keys."],"tags":["permissions","security","authorization","binding"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}