agalwood/Motrix · warning · AppError

PluginPermissionUnsupported

PluginPermissionUnsupported

Error message

plugin.grants.not_supported

What it means

Thrown by GrantsManager.updateGrants when the target plugin is a builtin or a dev plugin (entry.origin !== 'community' OR entry.dev is true). Only community plugins have user-revocable optional grants; builtins and dev plugins are trusted by construction and receive all declared permissions, so mutating their grants is meaningless and rejected. Classified PluginPermissionUnsupported.

Source

Thrown at src/core/plugin/grants/grants-manager.ts:64

   * declared in `manifest.optionalPermissions`; unknown keys (including
   * required permissions, which are never user-revocable) throw
   * `plugin.grants.unknown_permission`. Builtins / dev plugins throw
   * `plugin.grants.not_supported`.
   *
   * Emits `Events.PluginGrantsChanged` after the write; consumers
   * (PluginHost in Phase 2) deactivate the plugin so the next activation
   * picks up the new effective permissions.
   */
  async updateGrants(pluginId: string, patch: GrantsMap): Promise<GrantsMap> {
    const entry = this.opts.registry.get(pluginId)
    if (!entry) {
      throw new AppError(
        ErrorCode.PluginManifestInvalid,
        `plugin.grants.unknown_plugin: ${pluginId}`
      )
    }
    if (entry.origin !== 'community' || entry.dev) {
      throw new AppError(
        ErrorCode.PluginPermissionUnsupported,
        'plugin.grants.not_supported'
      )
    }

    const optional = new Set(entry.manifest.optionalPermissions ?? [])
    for (const key of Object.keys(patch)) {
      if (!optional.has(key)) {
        throw new AppError(
          ErrorCode.PluginPermissionUnsupported,
          `plugin.grants.unknown_permission: ${key}`
        )
      }
    }

    const record = await readInstallRecord(entry.rootDir)
    if (!record) {
      throw new AppError(

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Gate the grants UI/action on entry.origin === 'community' && !entry.dev before calling updateGrants.
  2. Use effectivePermissionsFor() to read the effective set for builtin/dev plugins instead of trying to mutate grants.
  3. If you genuinely need to change a dev plugin's permissions, edit its manifest permissions/optionalPermissions and reload, not its grants.

Example fix

// before
await grants.updateGrants(pluginId, patch)

// after
const entry = registry.get(pluginId)
if (entry && entry.origin === 'community' && !entry.dev) {
  await grants.updateGrants(pluginId, patch)
}
Defensive patterns

Strategy: validation

Validate before calling

const entry = registry.get(pluginId)
if (!entry || entry.origin !== 'community' || entry.dev) { /* grants not supported; skip */ }

Try / catch

try { await grants.updateGrants(pluginId, patch) }
catch (e) { if (e.message === 'plugin.grants.not_supported') { /* builtin/dev: read-only */ } else throw e }

Prevention

When it happens

Trigger: Calling updateGrants on a plugin whose registry entry has origin 'builtin'/'core' or dev:true, regardless of the patch contents.

Common situations: A settings UI lists all plugins uniformly and lets the user toggle grants on a builtin/dev plugin; a tooling script iterates all registry entries and calls updateGrants without filtering; a developer testing locally against a dev-mode plugin expects grants to apply.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/6175ffd4757911c7. Report an issue: GitHub.