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
- Always call the unregister function returned by register() when the owning plugin/component unmounts or disposes
- Raise maxHooksPerEvent in the HookManager constructor config when more than 50 legitimate hooks per event are expected
- 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
- Always store the unregister function returned by register() and call it on dispose
- Never register hooks inside per-request/per-message hot paths
- Deduplicate by pluginName+event before re-registering on reconnect/reload
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
- ruflo proxy supervisor failed to spawn — no PID returned
- maxOutputBytes must be positive
- Daemon '${config.name}' is already registered
- Daemon '${name}' not found
- Daemon '${name}' is disabled
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f638b4b4278932a3.
Report an issue: GitHub.