agalwood/Motrix · error · AppError

PluginManifestInvalid

PluginManifestInvalid

Error message

plugin.grants.unknown_plugin: ${pluginId}

What it means

Thrown by GrantsManager.updateGrants when the registry has no entry for pluginId — i.e. the plugin is not discovered/registered. Grants can only be mutated for plugins the host knows about. Classified as PluginManifestInvalid because the install/discovery state is inconsistent with the request.

Source

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

    if (!record) return {}
    return this.filterToOptional(pluginId, record.grants)
  }

  /**
   * Apply a patch over the current grants and persist. Patch keys must be
   * 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}`
        )

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Confirm the plugin is currently discovered: check registry.get(pluginId) or registry.entries() before calling updateGrants.
  2. Refresh the plugin list in the UI before allowing grants edits so stale ids are never sent.
  3. If the plugin was uninstalled, no grants mutation is meaningful — treat as a no-op or re-install first.
  4. Verify the exact id spelling against the manifest's id field.

Example fix

// before
await grants.updateGrants('acme.fetcher', { 'net.fetch': 'granted' })

// after
if (!registry.get('acme.fetcher')) throw new Error('plugin not installed')
await grants.updateGrants('acme.fetcher', { 'net.fetch': 'granted' })
Defensive patterns

Strategy: validation

Validate before calling

if (!registry.get(pluginId)) { throw new Error(`plugin ${pluginId} is not registered`) }

Try / catch

try { await grants.updateGrants(pluginId, patch) }
catch (e) { if (e.message.startsWith('plugin.grants.unknown_plugin')) { /* refresh plugin list */ } else throw e }

Prevention

When it happens

Trigger: Calling grantsManager.updateGrants(pluginId, patch) with an id that registry.get(pluginId) does not return — typo in the id, plugin not yet installed/discovered, or already uninstalled.

Common situations: A UI sends a grants update for a plugin id from a stale list (plugin was uninstalled since the list loaded), a developer typos the id, or the registry was reset/reloaded and the plugin hasn't been re-discovered yet.

Related errors


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