{"record":{"id":"63215ed525f7ca75","repo":"agalwood/Motrix","slug":"pluginmanifestinvalid-63215e","errorCode":"PluginManifestInvalid","errorMessage":"plugin.grants.unknown_plugin: ${pluginId}","messagePattern":"plugin\\.grants\\.unknown_plugin: (.+?)","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/grants/grants-manager.ts","lineNumber":58,"sourceCode":"    if (!record) return {}\n    return this.filterToOptional(pluginId, record.grants)\n  }\n\n  /**\n   * Apply a patch over the current grants and persist. Patch keys must be\n   * declared in `manifest.optionalPermissions`; unknown keys (including\n   * required permissions, which are never user-revocable) throw\n   * `plugin.grants.unknown_permission`. Builtins / dev plugins throw\n   * `plugin.grants.not_supported`.\n   *\n   * Emits `Events.PluginGrantsChanged` after the write; consumers\n   * (PluginHost in Phase 2) deactivate the plugin so the next activation\n   * picks up the new effective permissions.\n   */\n  async updateGrants(pluginId: string, patch: GrantsMap): Promise<GrantsMap> {\n    const entry = this.opts.registry.get(pluginId)\n    if (!entry) {\n      throw new AppError(\n        ErrorCode.PluginManifestInvalid,\n        `plugin.grants.unknown_plugin: ${pluginId}`\n      )\n    }\n    if (entry.origin !== 'community' || entry.dev) {\n      throw new AppError(\n        ErrorCode.PluginPermissionUnsupported,\n        'plugin.grants.not_supported'\n      )\n    }\n\n    const optional = new Set(entry.manifest.optionalPermissions ?? [])\n    for (const key of Object.keys(patch)) {\n      if (!optional.has(key)) {\n        throw new AppError(\n          ErrorCode.PluginPermissionUnsupported,\n          `plugin.grants.unknown_permission: ${key}`\n        )","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/grants/grants-manager.ts#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm the plugin is currently discovered: check registry.get(pluginId) or registry.entries() before calling updateGrants.","Refresh the plugin list in the UI before allowing grants edits so stale ids are never sent.","If the plugin was uninstalled, no grants mutation is meaningful — treat as a no-op or re-install first.","Verify the exact id spelling against the manifest's id field."],"exampleFix":"// before\nawait grants.updateGrants('acme.fetcher', { 'net.fetch': 'granted' })\n\n// after\nif (!registry.get('acme.fetcher')) throw new Error('plugin not installed')\nawait grants.updateGrants('acme.fetcher', { 'net.fetch': 'granted' })","handlingStrategy":"validation","validationCode":"if (!registry.get(pluginId)) { throw new Error(`plugin ${pluginId} is not registered`) }","typeGuard":null,"tryCatchPattern":"try { await grants.updateGrants(pluginId, patch) }\ncatch (e) { if (e.message.startsWith('plugin.grants.unknown_plugin')) { /* refresh plugin list */ } else throw e }","preventionTips":["Refresh the discovered plugin list before allowing grants edits.","Spell-check the id against the manifest.","Don't send grants updates for uninstalled plugins."],"tags":["plugin","grants","permissions","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}