{"record":{"id":"9980230513ff58a0","repo":"agalwood/Motrix","slug":"plugin-activation-cap-exceeded","errorCode":"PLUGIN_ACTIVATION_CAP_EXCEEDED","errorMessage":"active plugin cap (${this.maxActivePlugins}) reached","messagePattern":"active plugin cap \\((.+?)\\) reached","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/host/plugin-host.ts","lineNumber":236,"sourceCode":"    return promise\n  }\n\n  private async doActivate(pluginId: string): Promise<void> {\n    const indexed = this.opts.registry.get(pluginId)\n    if (!indexed) {\n      throw new AppError(\n        ErrorCode.PluginManifestInvalid,\n        `unknown plugin: ${pluginId}`\n      )\n    }\n    if (!indexed.state.enabled) {\n      throw new AppError(\n        ErrorCode.PluginRuntimeFault,\n        `plugin ${pluginId} is disabled`\n      )\n    }\n    if (this.active.size >= this.maxActivePlugins) {\n      throw new AppError(\n        ErrorCode.PluginActivationCapExceeded,\n        `active plugin cap (${this.maxActivePlugins}) reached`\n      )\n    }\n\n    const needsFfmpeg =\n      indexed.manifest.permissions.includes('ffmpeg') ||\n      (indexed.manifest.optionalPermissions ?? []).includes('ffmpeg')\n\n    let ffmpegDetection: FfmpegDetection = { available: false }\n    if (needsFfmpeg && this.opts.ffmpegDetect) {\n      ffmpegDetection = await this.opts.ffmpegDetect()\n    }\n\n    const ffmpegSatisfied =\n      ffmpegDetection.available &&\n      ffmpegSatisfies(\n        ffmpegDetection.version ?? '',","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/host/plugin-host.ts#L218-L254","documentation":"Activation is refused once the number of active plugins reaches maxActivePlugins (default 32, configurable via PluginHostOptions.maxActivePlugins), throwing PLUGIN_ACTIVATION_CAP_EXCEEDED. The cap bounds simultaneous VMs.","triggerScenarios":"Activating one more plugin while active.size is already >= maxActivePlugins.","commonSituations":"Many plugins enabled at once; cap lowered for a constrained environment; idle plugins not yet swept by the 5-min idle timer; a burst of concurrent activations.","solutions":["Deactivate an idle or lower-priority plugin first, then retry activate.","Wait for the idle sweep (5 min default) to reclaim VMs, or trigger it manually.","Raise maxActivePlugins in PluginHostOptions if the environment can sustain more VMs."],"exampleFix":"// before\nawait host.activate(id) // throws at cap\n// after — make room first\nif (host.activeCount >= host.maxActivePlugins) {\n  const idle = host.pickIdlePlugin()\n  if (idle) await host.deactivate(idle)\n}\nawait host.activate(id)","handlingStrategy":"fallback","validationCode":"function assertCapAvailable(activeCount: number, max: number): void {\n  if (activeCount >= max) {\n    throw new Error(`active plugin cap (${max}) reached; deactivate an idle plugin first`)\n  }\n}","typeGuard":"function hasCapRoom(activeCount: number, max: number): boolean {\n  return activeCount < max\n}","tryCatchPattern":"try {\n  await host.activate(pluginId)\n} catch (e) {\n  if (e.code === 'PLUGIN_ACTIVATION_CAP_EXCEEDED') {\n    const idle = host.pickIdlePlugin()\n    if (idle) { await host.deactivate(idle); await host.activate(pluginId) }\n    else throw e\n  } else throw e\n}","preventionTips":["Deactivate idle or lower-priority plugins before activating new ones when near the cap.","Expose and respect maxActivePlugins in the UI so users see the budget.","Trigger the idle sweep before reporting the cap as a hard failure."],"tags":["plugin","activation","quota"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}