moeru-ai/airi · error · Error

Cannot grant permissions to unknown plugin

Error message

Cannot grant permissions to unknown plugin "${extensionId}".

What it means

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.

Solutions

  1. Start/initialize the extension (host.start populates the snapshot) before enabling grant actions for it.
  2. Guard in the UI layer: disable 'Grant' until permissions.get(extensionId) returns a snapshot.
  3. Confirm the extensionId matches session.extension.id exactly.
  4. After host recreation, re-initialize permission state (optionally passing persisted grants via initialize options) instead of granting into the void.

Example fix

// before
function onGrantClick(extensionId: string, grant: ModulePermissionGrant) {
  svc.grant(extensionId, grant) // throws when not initialized
}

// after
function onGrantClick(extensionId: string, grant: ModulePermissionGrant) {
  if (!svc.get(extensionId)) {
    svc.initialize(extensionId, {})
  }
  svc.grant(extensionId, grant)
}
Defensive patterns

Strategy: validation

Validate before calling

// Before granting from a UI:
if (!permissions.get(extensionId)) {
  throw new Error(`Start extension '${extensionId}' before granting permissions`)
}
const snapshot = permissions.grant(extensionId, grant)

Type guard

const canGrant = (svc: PermissionService, extensionId: string): boolean => svc.get(extensionId) !== undefined

Try / catch

try {
  svc.grant(extensionId, grant)
} catch (error) {
  if (error instanceof Error && error.message.includes('unknown plugin')) {
    // session not started yet: queue the grant and apply it after initialize
    pendingGrants.set(extensionId, grant)
    return
  }
  throw error
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/c35c06f2e4c6c62d. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugin-sdk/src/plugin-host/runtimes/shared/services/permissions.ts:339

    if (!existing) {
      throw new Error(`Cannot declare permissions for unknown plugin "${extensionId}".`)
    }

    const requested = normalizeDeclaration(requestedDeclaration)
    const snapshot: PermissionSnapshot = {
      requested: mergePermissionDeclarations(existing.requested, requested),
      granted: existing.granted,
      revision: existing.revision + 1,
    }

    this.store.set(extensionId, snapshot)
    return snapshot
  }

  grant(extensionId: string, grant: ModulePermissionGrant) {
    const existing = this.store.get(extensionId)
    if (!existing) {
      throw new Error(`Cannot grant permissions to unknown plugin "${extensionId}".`)
    }

    const mergedGranted = mergePermissions(existing.granted, grant)
    const snapshot: PermissionSnapshot = {
      requested: existing.requested,
      granted: intersectPermissions(existing.requested, mergedGranted),
      revision: existing.revision + 1,
    }
    this.store.set(extensionId, snapshot)
    return snapshot
  }

  get(extensionId: string) {
    return this.store.get(extensionId)
  }

  isAllowed(extensionId: string, area: ModulePermissionArea, action: string, key: string) {
    const snapshot = this.store.get(extensionId)

View on GitHub (pinned to 677329427f)