mastra-ai/mastra · error · FactoryRuleValidationError
Factory rules.tools.${toolName} must be an object.
Error message
Factory rules.tools.${toolName} must be an object. What it means
Each entry under rules.tools must be a plain object containing only an optional onResult handler. This error fires when a tool's value is not a plain object (its name already passed boundedString with IDENTIFIER_RE, max 128 chars).
Source
Thrown at mastracode/factory/src/rules/validation.ts:171
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.`);
}
}
if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');
for (const [event, leaf] of Object.entries(rules.linear)) {View on GitHub (pinned to 75dd419e61)
Solutions
- Nest the handler: rules.tools.<toolName> = { onResult: handler }.
- Use {} for tools registered without an onResult handler.
- Replace null placeholders with {} or delete the entry.
Example fix
// before
tools: { 'run-tests': onTestResult }
// after
tools: { 'run-tests': { onResult: onTestResult } } Defensive patterns
Strategy: type-guard
Validate before calling
for (const [name, leaf] of Object.entries(rules.tools ?? {})) {
if (/^[a-z0-9][a-z0-9_-]*$/i.test(name) === false) throw new Error(`Invalid tool name: ${name}`);
if (leaf === null || typeof leaf !== 'object' || Array.isArray(leaf)) {
throw new Error(`tools.${name} must be { onResult?: fn }`);
}
} Type guard
const isToolLeaf = (v: unknown): v is { onResult?: (result: unknown) => 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)) {
const tool = e.message.match(/tools\.(\S+) must/)?.[1];
console.error(`Tool rule for '${tool}' must be { onResult?: fn }`);
} else throw e;
} Prevention
- Nest tool handlers under onResult; never assign a bare function as the tool's value.
- Type rules.tools as Record<string, { onResult?: ToolHandler }>.
- Use {} for tools without result handlers instead of null placeholders.
When it happens
Trigger: rules.tools.<toolName> set directly to a callback function, null, an array, or a string instead of { onResult: fn }, while validating via assertFactoryRules.
Common situations: Writing tools: { runTests: onTestResult } instead of nesting under onResult; null placeholders for tools with no handler; spreads that flattened the expected leaf level.
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}.${source} must be an object.
- Factory rules.tools must be an object.
- Factory rules.tools.${toolName}.onResult must be a function.
- ${label}.${stage} must be an object.
- ${label}.${stage}.${source} handlers must be functions.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/98f64eb074d97ac6.
Report an issue: GitHub.