agalwood/Motrix · warning · AppError

PluginActivationCapExceeded

PluginActivationCapExceeded

Error message

plugin.lifecycle.activation_cap_exceeded

What it means

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.

Source

Thrown at src/core/plugin/host/activation-dispatcher.ts:129

  async admit(
    matchingInactive: string[],
    event: HostActivationEvent
  ): Promise<void> {
    const needed = this.host.activeIds().length + matchingInactive.length
    if (needed <= this.maxActive) {
      for (const id of matchingInactive) {
        await this.host.activate(id)
      }
      return
    }
    const slotsNeeded = needed - this.maxActive
    const criticalSet = this.deriveCriticalSet(event)
    const freed = await this.runEviction(slotsNeeded, criticalSet)
    if (freed < slotsNeeded) {
      this.emitter?.emit('plugin.activation_cap_exceeded', {
        unfittable: matchingInactive.filter((id) => !this.host.isActive(id)),
      })
      throw new AppError(
        ErrorCode.PluginActivationCapExceeded,
        'plugin.lifecycle.activation_cap_exceeded'
      )
    }
    for (const id of matchingInactive) {
      await this.host.activate(id)
    }
  }

  /**
   * Derive the set of plugin ids that must not be evicted for this event.
   *
   * T14 placeholder: always returns an empty set. The tier-ordering in
   * `runEviction` already protects critical roles by evicting 'audit' first.
   * T15 will replace this with real in-flight tracking via
   * `PluginHost.activeWithInFlightHook(taskId)` once TaskManager wires the
   * HookOrchestrator.
   */

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Raise maxActive in the ActivationDispatcher constructor options (or DEFAULT_MAX_ACTIVE_PLUGINS).
  2. Narrow plugin activationEvents so fewer plugins match a given event (avoid '*' / broad onTaskType).
  3. Disable or uninstall plugins that are not needed to reduce steady-state active count.
  4. If you control the emitter, handle 'plugin.activation_cap_exceeded' to surface the unfittable ids to the user rather than letting the throw propagate.

Example fix

// before
new ActivationDispatcher(registry, host, { maxActive: 4 })
// after
new ActivationDispatcher(registry, host, { maxActive: 16 })
Defensive patterns

Strategy: try-catch

Validate before calling

// Before dispatch, estimate whether admission will fit.
const projected = host.activeIds().length + matchingInactive.length
if (projected > maxActive) {
  // pre-emptively narrow the matching set or surface a warning
}

Try / catch

try {
  await dispatcher.dispatch(event)
} catch (e) {
  if (e instanceof AppError && e.code === ErrorCode.PluginActivationCapExceeded) {
    // degraded mode: log and continue; the unfittable ids were emitted
    return
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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