agalwood/Motrix · error · AppError

PLUGIN_MANIFEST_INVALID

PLUGIN_MANIFEST_INVALID

Error message

unknown plugin: ${pluginId}

What it means

doActivate looks up pluginId in the registry; if registry.get returns nothing, it throws PLUGIN_MANIFEST_INVALID. The id was never installed/indexed, was removed, or does not match an installed plugin's id.

Source

Thrown at src/core/plugin/host/plugin-host.ts:224

    }
    // Coalesce concurrent activations. `active` is only populated after the
    // awaits in doActivate (ffmpeg detect, bundle read, permission
    // resolution), so two overlapping calls would otherwise both pass the
    // guard above, both spawn a worker, and the second active.set() would
    // orphan the first bridge. Share one in-flight promise keyed by pluginId.
    const inFlight = this.activating.get(pluginId)
    if (inFlight) return inFlight
    const promise = this.doActivate(pluginId).finally(() => {
      this.activating.delete(pluginId)
    })
    this.activating.set(pluginId, promise)
    return promise
  }

  private async doActivate(pluginId: string): Promise<void> {
    const indexed = this.opts.registry.get(pluginId)
    if (!indexed) {
      throw new AppError(
        ErrorCode.PluginManifestInvalid,
        `unknown plugin: ${pluginId}`
      )
    }
    if (!indexed.state.enabled) {
      throw new AppError(
        ErrorCode.PluginRuntimeFault,
        `plugin ${pluginId} is disabled`
      )
    }
    if (this.active.size >= this.maxActivePlugins) {
      throw new AppError(
        ErrorCode.PluginActivationCapExceeded,
        `active plugin cap (${this.maxActivePlugins}) reached`
      )
    }

    const needsFfmpeg =

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Look the plugin up via registry.get(id) (or listInstalled) before calling activate, and surface the available ids.
  2. Re-scan/reload the registry if the id should exist.
  3. Strip whitespace and match id casing exactly against the installed plugin id.

Example fix

// before
await host.activate('transcoder') // wrong id
// after
const ids = host.registry.list().map(p => p.id)
const id = ids.find(x => x.toLowerCase() === 'transcoder')
if (!id) throw new Error(`unknown plugin; installed: ${ids.join(', ')}`)
await host.activate(id)
Defensive patterns

Strategy: validation

Validate before calling

function assertPluginKnown(pluginId: string, registry: { get(id: string): unknown; list(): Array<{ id: string }> }): void {
  if (!registry.get(pluginId)) {
    const known = registry.list().map(p => p.id).join(', ')
    throw new Error(`unknown plugin ${pluginId}; installed: ${known}`)
  }
}

Type guard

function isInstalledPlugin(pluginId: string, registry: { get(id: string): unknown }): boolean {
  return registry.get(pluginId) != null
}

Try / catch

try {
  await host.activate(pluginId)
} catch (e) {
  if (e.code === 'PLUGIN_MANIFEST_INVALID' && /unknown plugin/.test(e.message)) {
    // re-scan the registry, refresh the id list, and prompt the user to pick
  } else throw e
}

Prevention

When it happens

Trigger: activate('foo') where 'foo' is not in the registry; id casing or whitespace mismatch; the plugin was uninstalled before activation resolved; caller passed an id from a stale list.

Common situations: Caller uses a pluginId from a cached/stale list; uninstall race; typo or copy-paste of the id; id sourced from a config that references a plugin no longer installed.

Related errors


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