ruvnet/ruflo · error · Error

Maximum hooks limit reached for event ${event}

Error message

Maximum hooks limit reached for event ${event}

What it means

HookManager.register() enforces a hard cap of config.maxHooksPerEvent (default 50) hooks per event name and throws when the next registration would exceed it. The cap protects the dispatch loop from unbounded fan-out; registrations are not queued or silently dropped — the caller must handle the overflow.

Source

Thrown at v3/@claude-flow/plugins/src/hooks/index.ts:82

      parallelExecution: false,
      ...config,
    };
  }

  /**
   * Register a hook.
   */
  register(hook: HookDefinition, pluginName?: string): () => void {
    const event = hook.event;

    if (!this.hooks.has(event)) {
      this.hooks.set(event, []);
    }

    const entries = this.hooks.get(event)!;

    if (entries.length >= (this.config.maxHooksPerEvent ?? 50)) {
      throw new Error(`Maximum hooks limit reached for event ${event}`);
    }

    const entry: HookEntry = {
      hook,
      pluginName,
      registeredAt: new Date(),
      executionCount: 0,
      avgExecutionTime: 0,
    };

    // Insert in priority order (higher priority first)
    const priority = hook.priority ?? HookPriorityEnum.Normal;
    const insertIndex = entries.findIndex(e => (e.hook.priority ?? HookPriorityEnum.Normal) < priority);

    if (insertIndex === -1) {
      entries.push(entry);
    } else {
      entries.splice(insertIndex, 0, entry);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Always call the unregister function returned by register() when the owning plugin/component unmounts or disposes
  2. Raise maxHooksPerEvent in the HookManager constructor config when more than 50 legitimate hooks per event are expected
  3. Deduplicate: key registrations by pluginName + event and skip re-registering an identical hook

Example fix

// before
class Worker {
  constructor(hooks: HookManager) {
    hooks.register({ event: 'task:before', handler: onTask }); // leaked every construction
  }
}

// after
class Worker {
  private unregister: () => void;
  constructor(hooks: HookManager) {
    this.unregister = hooks.register({ event: 'task:before', handler: onTask });
  }
  dispose(): void { this.unregister(); }
}

// or, if you truly need more:
new HookManager({ maxHooksPerEvent: 200 })
Defensive patterns

Strategy: validation

Validate before calling

// Track and dispose registrations; raise the cap when legitimate:
const unregister = hooks.register({ event: 'task:before', handler: onTask });
// on teardown:
unregister();

// when >50 hooks per event are expected up front:
const hooks = new HookManager({ maxHooksPerEvent: 250 });

Type guard

type HookDefinitionLike = { event: string; handler?: unknown };
function hasHandler(h: HookDefinitionLike): boolean {
  return typeof h.handler === 'function';
}

Prevention

When it happens

Trigger: Registering the 51st hook for a single event with default config; registering hooks inside a hot path (per request, per task, per component mount) without ever calling the unregister function returned by register(); loading many plugin instances that each add hooks to the same event like 'task:before'.

Common situations: Effects/handlers registered on every request or message without cleanup, accumulating leaks until the cap trips on a long-lived daemon or bot; test suites sharing one HookManager across hundreds of registrations; legitimately needing more than 50 hooks per event in a large plugin fleet.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/f638b4b4278932a3. Report an issue: GitHub.