{"record":{"id":"6175ffd4757911c7","repo":"agalwood/Motrix","slug":"pluginpermissionunsupported","errorCode":"PluginPermissionUnsupported","errorMessage":"plugin.grants.not_supported","messagePattern":"plugin\\.grants\\.not_supported","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"warning","filePath":"src/core/plugin/grants/grants-manager.ts","lineNumber":64,"sourceCode":"   * 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        )\n      }\n    }\n\n    const record = await readInstallRecord(entry.rootDir)\n    if (!record) {\n      throw new AppError(","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/grants/grants-manager.ts#L46-L82","documentation":"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.","triggerScenarios":"Calling updateGrants on a plugin whose registry entry has origin 'builtin'/'core' or dev:true, regardless of the patch contents.","commonSituations":"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.","solutions":["Gate the grants UI/action on entry.origin === 'community' && !entry.dev before calling updateGrants.","Use effectivePermissionsFor() to read the effective set for builtin/dev plugins instead of trying to mutate grants.","If you genuinely need to change a dev plugin's permissions, edit its manifest permissions/optionalPermissions and reload, not its grants."],"exampleFix":"// before\nawait grants.updateGrants(pluginId, patch)\n\n// after\nconst entry = registry.get(pluginId)\nif (entry && entry.origin === 'community' && !entry.dev) {\n  await grants.updateGrants(pluginId, patch)\n}","handlingStrategy":"validation","validationCode":"const entry = registry.get(pluginId)\nif (!entry || entry.origin !== 'community' || entry.dev) { /* grants not supported; skip */ }","typeGuard":null,"tryCatchPattern":"try { await grants.updateGrants(pluginId, patch) }\ncatch (e) { if (e.message === 'plugin.grants.not_supported') { /* builtin/dev: read-only */ } else throw e }","preventionTips":["Gate grants UI on origin === 'community' && !dev.","Use effectivePermissionsFor() to read builtin/dev effective permissions.","Edit manifests (not grants) to change dev plugin permissions."],"tags":["plugin","grants","permissions","access-control"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}