mastra-ai/mastra · error · FactoryRuleValidationError

Factory rules.github.${event}.onEvent must be a function.

Error message

Factory rules.github.${event}.onEvent must be a function.

What it means

Thrown by assertFactoryRules when a GitHub event leaf object contains an onEvent key whose value is defined but not a function. onEvent is the event handler hook and must be callable. Validation runs eagerly in prepare/defaultFactoryRules so a bad handler type is rejected at configuration time.

Source

Thrown at mastracode/factory/src/rules/validation.ts:184

  if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');
  for (const [toolName, leaf] of Object.entries(rules.tools)) {
    boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);
    if (!isPlainObject(leaf))
      throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);
    assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);
    if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {
      throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);
    }
  }

  if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError('Factory rules.github must be an object.');
  for (const [event, leaf] of Object.entries(rules.github)) {
    enumValue(event, FACTORY_GITHUB_EVENTS, 'Factory GitHub event');
    if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);
    assertExactKeys(leaf, ['onEvent'], `Factory rules.github.${event}`);
    if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {
      throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);
    }
  }

  if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');
  for (const [event, leaf] of Object.entries(rules.linear)) {
    enumValue(event, FACTORY_LINEAR_EVENTS, 'Factory Linear event');
    if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.linear.${event} must be an object.`);
    assertExactKeys(leaf, ['onEvent'], `Factory rules.linear.${event}`);
    if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {
      throw new FactoryRuleValidationError(`Factory rules.linear.${event}.onEvent must be a function.`);
    }
  }
}

function commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {
  return {
    idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),
  };

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure the onEvent value is an actual function reference: { onEvent: handleIssueCommented }
  2. If handlers are configured by name, resolve the name to a function before calling prepare
  3. Check for accidental string wrapping, e.g. { onEvent: 'handler.ts' } instead of an import
  4. Omit onEvent entirely (or set it to undefined) when no handler is needed instead of a placeholder value

Example fix

// before
{ github: { issue_commented: { onEvent: 'handlers/issueCommented' } } }
// after
import { issueCommented } from './handlers';
{ github: { issue_commented: { onEvent: issueCommented } } }
Defensive patterns

Strategy: validation

Validate before calling

for (const [event, leaf] of Object.entries(rules.github ?? {})) {
  const h = leaf?.onEvent;
  if (h !== undefined && typeof h !== 'function') {
    throw new TypeError(`rules.github.${event}.onEvent must be a function, got ${typeof h}`);
  }
}

Type guard

const hasFnOnEvent = (leaf) =>
  leaf?.onEvent === undefined || typeof leaf.onEvent === 'function';

Try / catch

try {
  prepare({ rules });
} catch (e) {
  if (e instanceof FactoryRuleValidationError && e.message.includes('.onEvent must be a function')) {
    console.error('Fix onEvent handler reference:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Setting rules.github[event] = { onEvent: 'notAFunction' } or { onEvent: true } or { onEvent: 42 }; passing a thenable/Promise instead of a function; forgetting to spread a handler map so a non-function value lands in onEvent.

Common situations: Loading handler names as strings from config intending to resolve them later; a variable holding the handler being undefined due to a bad import (undefined passes since only defined non-functions fail — but a string default from env does not); typo assigning the handler to onEvent via string interpolation.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/4843ba60e2b87f3d. Report an issue: GitHub.