ruvnet/ruflo · error · Error

Hook for event ${this.event} requires a handler

Error message

Hook for event ${this.event} requires a handler

What it means

HookBuilder.build() validates that a handler was set via .handle(fn) and throws otherwise. It is builder validation: the resulting HookDefinition would be unexecutable, so build() refuses to emit it. Conditions and transformers are optional; only the handler is mandatory.

Source

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

  transform(transformer: (data: unknown) => unknown): this {
    this.transformers.push(transformer);
    return this;
  }

  /**
   * Set the handler function.
   */
  handle(handler: HookHandler): this {
    this.handler = handler;
    return this;
  }

  /**
   * Build the hook definition.
   */
  build(): HookDefinition {
    if (!this.handler) {
      throw new Error(`Hook for event ${this.event} requires a handler`);
    }

    const originalHandler = this.handler;
    const conditions = this.conditions;
    const transformers = this.transformers;

    // Wrap handler with conditions and transformers
    const wrappedHandler: HookHandler = async (context: HookContext) => {
      // Check conditions
      for (const condition of conditions) {
        if (!condition(context)) {
          return { success: true, data: context.data };
        }
      }

      // Apply transformers
      let data = context.data;
      for (const transformer of transformers) {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Add .handle(fn) to the chain before .build()
  2. When the handler is genuinely optional-by-branch, provide a no-op default handler or only call build() once a handler is assigned
  3. Centralize builder completion in a single factory function that always sets a handler as its last step

Example fix

// before
const hook = builder.on('task:after').when(ctx => ctx.ok).build(); // throws

// after
const hook = builder
  .on('task:after')
  .when(ctx => ctx.ok)
  .handle(async ctx => ({ success: true, data: ctx.data }))
  .build();
Defensive patterns

Strategy: validation

Validate before calling

// Complete the builder in one factory so the handler can never be missing:
function buildHook(
  builder: HookBuilder,
  handler: HookHandler | undefined
): HookDefinition {
  if (!handler) {
    throw new Error('refusing to build hook without a handler');
  }
  return builder.handle(handler).build();
}

Prevention

When it happens

Trigger: Building a fluent chain that is missing .handle(): builder.on('x').when(cond).build(); conditional wiring where the branch that calls .handle() is skipped by a typo'd if or a falsy check; refactors that renamed the handle() call or moved it after build().

Common situations: Copy-pasted builder code with the handle line dropped; dynamically assembled builders whose handler depends on a runtime branch; incomplete first-draft code passing tests of construction but failing at build().

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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