mastra-ai/mastra · error · FactoryRuleValidationError

Factory rule causal depth exceeded.

Error message

Factory rule causal depth exceeded.

What it means

validateFactoryRuleDecision guards against unbounded recursive/nested causal decisions via a causalDepth counter (default 0). If a decision chain exceeds MAX_FACTORY_RULE_CAUSAL_DEPTH, this error is thrown to stop runaway recursion. Each nested decision validation increments the depth.

Source

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

  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),
  };
}

export function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {
  if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {
    throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');
  }
  if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');
  const type = value.type;
  if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');

  switch (type) {
    case 'reject': {
      assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');
      return {
        type,
        code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),
        reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),
      };
    }
    case 'transition': {
      assertExactKeys(
        value,
        ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Break the recursion: stop returning decisions that embed prior decision objects in causal chains
  2. Flatten the decision payload — pass IDs/references instead of nested decision objects
  3. Increase review of reenter flags that let a rule re-fire on its own transition
  4. Inspect MAX_FACTORY_RULE_CAUSAL_DEPTH in validation.ts to know the limit and count your chain depth

Example fix

// before
const decision = { type: 'transition', ..., causal: parentDecision } // nests prior decision
// after
const decision = { type: 'transition', ..., causalId: parentDecision.idempotencyKey }
Defensive patterns

Strategy: validation

Validate before calling

function assertCausalChainDepth(decision, max = MAX_DEPTH) {
  let d = decision, depth = 0;
  while (d?.causal) { d = d.causal; if (++depth > max) throw new Error('causal chain too deep'); }
}

Type guard

const isShallowDecision = (d, max = 3) => {
  let cur = d, depth = 0;
  while (cur?.causal) { cur = cur.causal; if (++depth > max) return false; }
  return true;
};

Try / catch

try {
  return decision(input);
} catch (e) {
  if (e instanceof FactoryRuleValidationError && e.message === 'Factory rule causal depth exceeded.') {
    logger.error('Runaway rule recursion; flattening decision chain');
    return decision(flattenDecision(input));
  }
  throw e;
}

Prevention

When it happens

Trigger: A rule's onEvent handler returning a decision that itself references/produces nested decisions recursively, e.g. a decision whose payload contains another decision chain longer than the max depth; mutually recursive rules feeding each other's decisions.

Common situations: A transition decision's message or linked data embedding previously produced decisions in a loop; rules that re-trigger themselves (possibly combined with reenter: true) causing ever-deeper causal chains.

Related errors


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