ruvnet/ruflo · error
Events and contexts arrays must have same length
Error message
Events and contexts arrays must have same length
What it means
ParallelHookExecutor.executeParallel(events, contexts, options) zips two arrays by index: events[i] is executed with contexts[i], and batches of size maxParallel are run with Promise.allSettled. It throws synchronously before any hook runs when the two arrays have different lengths, because there is no defensible pairing. This is a caller precondition violation, not a hook execution failure.
Source
Thrown at v3/@claude-flow/shared/src/hooks/executor.ts:247
};
}
}
/**
* Execute multiple hooks in parallel
*
* @param events - Array of hook events
* @param contexts - Array of contexts (matched by index)
* @param options - Execution options
* @returns Array of aggregated results
*/
async executeParallel(
events: HookEvent[],
contexts: HookContext[],
options: HookExecutionOptions = {}
): Promise<AggregatedHookResult[]> {
if (events.length !== contexts.length) {
throw new Error('Events and contexts arrays must have same length');
}
const maxParallel = options.maxParallel || events.length;
const results: AggregatedHookResult[] = [];
// Execute in batches
for (let i = 0; i < events.length; i += maxParallel) {
const batch = events.slice(i, i + maxParallel);
const batchContexts = contexts.slice(i, i + maxParallel);
const batchResults = await Promise.allSettled(
batch.map((event, index) =>
this.execute(event, batchContexts[index], options)
)
);
for (const result of batchResults) {
if (result.status === 'fulfilled') {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Derive contexts from events so lengths are structurally equal: const contexts = events.map(e => contextFor(e))
- If one shared context applies to every event, pass events.map(() => sharedContext) instead of [sharedContext]
- Add a dev-time assertion events.length === contexts.length at the construction site so the bug is caught where the arrays are built, not inside the executor
- If the two lists legitimately differ, loop over execute(event, context, options) instead of executeParallel()
Example fix
// before const events = enabledHooks.map(h => h.event); // 3 entries const contexts = [context]; // 1 entry await executor.executeParallel(events, contexts); // throws // after const events = enabledHooks.map(h => h.event); const contexts = events.map(() => context); // matched by index await executor.executeParallel(events, contexts);
Defensive patterns
Strategy: validation
Validate before calling
if (events.length !== contexts.length) {
throw new RangeError(
`executeParallel precondition failed: ${events.length} events vs ${contexts.length} contexts`
);
}
await executor.executeParallel(events, contexts); Type guard
function isIndexMatched<T, U>(events: T[], contexts: U[]): boolean {
return Array.isArray(events) && Array.isArray(contexts) && events.length === contexts.length;
} Try / catch
try {
await executor.executeParallel(events, contexts);
} catch (e) {
if (e instanceof Error && e.message.includes('same length')) {
// caller-side data bug: rebuild the arrays, do NOT retry with the same inputs
throw new Error(`hook batch malformed: ${events.length} events / ${contexts.length} contexts`);
}
throw e;
} Prevention
- Derive contexts from events with .map() so the arrays cannot diverge
- Keep a single array of { event, context } tuples and unzip only at the call boundary
- Apply identical filters to both arrays, or filter the tuple array before unzipping
When it happens
Trigger: Calling executeParallel(['pre-task','post-task'], [ctx]) after appending an event without its context; building events with a filter (e.g. only enabled hooks) while passing an unfiltered contexts array; reusing a cached contexts array after the events list changed; passing events with contexts.length === 0.
Common situations: Refactoring from execute() (single event + single context) to executeParallel() and forgetting the contexts array must grow too; dynamic event lists composed at runtime paired with a static one-element context list; copy-pasting a call site and editing one array but not the other.
Related errors
- Issue ${input.issueId} is not stealable or not claimed
- refusing to prepare writing worktrees from a dirty repositor
- 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/2ef36ccfdb642131.
Report an issue: GitHub.