mastra-ai/mastra · error · FactoryRuleValidationError
Factory rules.tools must be an object.
Error message
Factory rules.tools must be an object.
What it means
assertFactoryRules requires rules.tools to be a plain object mapping tool names to { onResult? } leaves. This error fires when rules.tools is missing, null, an array, or another non-plain-object value.
Source
Thrown at mastracode/factory/src/rules/validation.ts:167
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.');
assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'github', 'linear'], 'Factory rules');
boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);
validateBoardRules(rules.work, 'Factory rules.work');
validateBoardRules(rules.review, 'Factory rules.review');
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.`);
}View on GitHub (pinned to 75dd419e61)
Solutions
- Set rules.tools to an object, using {} when there are no tool rules.
- Migrate legacy configs that lacked a tools section to include tools: {}.
- Check merge logic (e.g. deepMerge) for the tools key returning null on conflict.
Example fix
// before
{ version: '1', work: {}, review: {}, tools: null, github: {}, linear: {} }
// after
{ version: '1', work: {}, review: {}, tools: {}, github: {}, linear: {} } Defensive patterns
Strategy: validation
Validate before calling
if (rules.tools !== undefined && (rules.tools === null || typeof rules.tools !== 'object' || Array.isArray(rules.tools))) {
throw new Error('rules.tools must be a plain object (use {} if empty)');
} Type guard
const isToolsMap = (v: unknown): v is Record<string, { onResult?: (...args: never[]) => unknown }> =>
typeof v === 'object' && v !== null && !Array.isArray(v); Try / catch
try {
assertFactoryRules(rules);
} catch (e) {
if (e.name === 'FactoryRuleValidationError' && /rules\.tools must be an object/.test(e.message)) {
rules.tools = {}; // or surface a config-migration error to the user
assertFactoryRules(rules);
} else throw e;
} Prevention
- Include tools: {} in every rules template/scaffold.
- When migrating configs between schema versions, add defaults for newly required sections.
- Deep-merge configs with a policy that never yields null for object-typed keys.
When it happens
Trigger: rules.tools set to null/undefined/array while the other required keys (version, work, review, github, linear) are present, during prepare or defaultFactoryRules.
Common situations: Tools rules omitted from a hand-written config and set to null as placeholder; older config format without a tools section upgraded to a version requiring the key; YAML/JSON merging that produced an empty string or list.
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
- ${label}.${stage} must be an object.
- ${label}.${stage}.${source} must be an object.
- Factory rules.tools.${toolName} must be an object.
- Factory rules.github must be an object.
- Factory rule version is required.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/bcda271753a5f002.
Report an issue: GitHub.