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
- 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.
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
- Gate grants UI on origin === 'community' && !dev.
- Use effectivePermissionsFor() to read builtin/dev effective permissions.
- Edit manifests (not grants) to change dev plugin permissions.
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
- PluginManifestInvalid
- plugin.commands.access_denied
- PluginRuntimeFault
- PluginRuntimeFault
- PluginRuntimeFault
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/6175ffd4757911c7.
Report an issue: GitHub.