{"record":{"id":"df616a602dc01c1c","repo":"agalwood/Motrix","slug":"pluginactivationcapexceeded","errorCode":"PluginActivationCapExceeded","errorMessage":"plugin.lifecycle.activation_cap_exceeded","messagePattern":"plugin\\.lifecycle\\.activation_cap_exceeded","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"warning","filePath":"src/core/plugin/host/activation-dispatcher.ts","lineNumber":129,"sourceCode":"  async admit(\n    matchingInactive: string[],\n    event: HostActivationEvent\n  ): Promise<void> {\n    const needed = this.host.activeIds().length + matchingInactive.length\n    if (needed <= this.maxActive) {\n      for (const id of matchingInactive) {\n        await this.host.activate(id)\n      }\n      return\n    }\n    const slotsNeeded = needed - this.maxActive\n    const criticalSet = this.deriveCriticalSet(event)\n    const freed = await this.runEviction(slotsNeeded, criticalSet)\n    if (freed < slotsNeeded) {\n      this.emitter?.emit('plugin.activation_cap_exceeded', {\n        unfittable: matchingInactive.filter((id) => !this.host.isActive(id)),\n      })\n      throw new AppError(\n        ErrorCode.PluginActivationCapExceeded,\n        'plugin.lifecycle.activation_cap_exceeded'\n      )\n    }\n    for (const id of matchingInactive) {\n      await this.host.activate(id)\n    }\n  }\n\n  /**\n   * Derive the set of plugin ids that must not be evicted for this event.\n   *\n   * T14 placeholder: always returns an empty set. The tier-ordering in\n   * `runEviction` already protects critical roles by evicting 'audit' first.\n   * T15 will replace this with real in-flight tracking via\n   * `PluginHost.activeWithInFlightHook(taskId)` once TaskManager wires the\n   * HookOrchestrator.\n   */","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/host/activation-dispatcher.ts#L111-L147","documentation":"Thrown by ActivationDispatcher.admit() when the two-pass eviction (idle-LRU then tier-aware) could not free enough slots to fit the newly matching inactive plugins within maxActive. Before throwing it emits the 'plugin.activation_cap_exceeded' event carrying the unfittable plugin ids, then raises AppError with ErrorCode.PluginActivationCapExceeded.","triggerScenarios":"host.activeIds().length + matchingInactive.length > maxActive AND runEviction(slotsNeeded, criticalSet) returns fewer than slotsNeeded freed slots. This happens when too few plugins are idle (>60s) and too few sit in evictable tiers (audit/enrich/post-process/resolve/pre-resolve) outside the critical set.","commonSituations":"maxActive / DEFAULT_MAX_ACTIVE_PLUGINS configured too low for the workload; many plugins all match onStartup or onTaskType simultaneously; all active plugins are recently busy (idle < 60s) so Pass 1 finds nothing; critical set (deriveCriticalSet) is over-broad in a future revision; user has installed many plugins with overlapping activationEvents.","solutions":["Raise maxActive in the ActivationDispatcher constructor options (or DEFAULT_MAX_ACTIVE_PLUGINS).","Narrow plugin activationEvents so fewer plugins match a given event (avoid '*' / broad onTaskType).","Disable or uninstall plugins that are not needed to reduce steady-state active count.","If you control the emitter, handle 'plugin.activation_cap_exceeded' to surface the unfittable ids to the user rather than letting the throw propagate."],"exampleFix":"// before\nnew ActivationDispatcher(registry, host, { maxActive: 4 })\n// after\nnew ActivationDispatcher(registry, host, { maxActive: 16 })","handlingStrategy":"try-catch","validationCode":"// Before dispatch, estimate whether admission will fit.\nconst projected = host.activeIds().length + matchingInactive.length\nif (projected > maxActive) {\n  // pre-emptively narrow the matching set or surface a warning\n}","typeGuard":null,"tryCatchPattern":"try {\n  await dispatcher.dispatch(event)\n} catch (e) {\n  if (e instanceof AppError && e.code === ErrorCode.PluginActivationCapExceeded) {\n    // degraded mode: log and continue; the unfittable ids were emitted\n    return\n  }\n  throw e\n}","preventionTips":["Subscribe to the 'plugin.activation_cap_exceeded' event to surface unfittable ids to the user.","Keep activationEvents narrow so simultaneous matches stay within maxActive.","Disable plugins the user does not need to lower steady-state active count."],"tags":["plugin","activation","resource-limits","lifecycle"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}