mastra-ai/mastra · error · FactoryRuleValidationError

${label} must be an object.

Error message

${label} must be an object.

What it means

validateBoardRules asserts each rule board (work/review) is a plain object mapping stages to sources. If the whole board value passed via assertFactoryRules is not a plain object (null, array, class instance, primitive), this error names the failing board via the label argument.

Source

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

    }
    return sanitized;
  } finally {
    seen.delete(value as object);
  }
}

function sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {
  if (value === undefined) return undefined;
  const sanitized = normalizeFactoryRuleJsonValue(value);
  if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');
  if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {
    throw new FactoryRuleValidationError('Rule metadata is too large.');
  }
  return sanitized;
}

function validateBoardRules(rules: unknown, label: string): asserts rules is FactoryBoardRules {
  if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);
  for (const [stage, sources] of Object.entries(rules)) {
    enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);
    if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);
    for (const [source, leaf] of Object.entries(sources)) {
      enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);
      if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);
      assertExactKeys(leaf, ['onEnter', 'onExit'], `${label}.${stage}.${source}`);
      for (const handler of Object.values(leaf)) {
        if (handler !== undefined && typeof handler !== 'function') {
          throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);
        }
      }
    }
  }
}

export function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {
  if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Set the board to an object keyed by valid stage names, e.g. { plan: {} }.
  2. If a board is unused, pass an empty object {} instead of null.
  3. Check whether config deserialization (e.g. JSON.parse) produced null for missing keys and default it: rules.work ?? {}.

Example fix

// before
const rules = { version: '1', work: null, review: {}, tools: {}, github: {}, linear: {} };
// after
const rules = { version: '1', work: {}, review: {}, tools: {}, github: {}, linear: {} };
Defensive patterns

Strategy: validation

Validate before calling

function isBoardRules(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}
if (!isBoardRules(rules.work) || !isBoardRules(rules.review)) {
  throw new Error('rules.work and rules.review must be plain objects (use {} if empty)');
}

Type guard

const isPlainRecord = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v) &&
  (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);

Try / catch

try {
  assertFactoryRules(rules);
} catch (e) {
  if (e.name === 'FactoryRuleValidationError' && e.code === 'invalid_factory_rule') {
    console.error(`Invalid rules config: ${e.message}`); // fix config and reload
  } else throw e;
}

Prevention

When it happens

Trigger: Calling assertFactoryRules / prepare / defaultFactoryRules with rules where rules.work or rules.review is null, an array, a Map/class instance, a string, or missing-but-explicitly-non-object (e.g. set to false).

Common situations: Hand-written rules config where work: null as a placeholder; JSON parsed from a config file where a board was omitted and defaulted to null; assigning an array of rules by mistake.

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/d813deba3e525c8c. Report an issue: GitHub.