mastra-ai/mastra · error · FactoryRuleValidationError
Factory transition message must be an object.
Error message
Factory transition message must be an object.
What it means
For a 'transition' decision, the optional message field must be a plain object shaped { text, role? }. If message is present but not a plain object, this error is thrown. The message is later attached to the transition and validated further (exact keys, bounded text/role).
Source
Thrown at mastracode/factory/src/rules/validation.ts:234
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'],
'Factory transition decision',
);
if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {
throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');
}
let message: { text: string; role?: string } | undefined;
if (value.message !== undefined) {
if (!isPlainObject(value.message)) {
throw new FactoryRuleValidationError('Factory transition message must be an object.');
}
assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');
const role =
value.message.role === undefined
? undefined
: boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);
message = {
text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),
...(role ? { role } : {}),
};
}
return {
type,
...commonCommitFields(value),
board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory transition board'),
stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory transition stage'),
...(message ? { message } : {}),
...(value.reenter === true ? { reenter: true } : {}),View on GitHub (pinned to 75dd419e61)
Solutions
- Wrap text: message: { text: 'Stage updated' } optionally with role: { text, role: 'system' }
- Omit the message key entirely instead of passing null
- Convert SDK/framework message objects into the plain { text, role? } shape before calling decision()
- Pre-check with isPlainObject-like guard: v && typeof v === 'object' && !Array.isArray(v)
Example fix
// before
decision({ type: 'transition', ..., message: `Moved to ${stage}` })
// after
decision({ type: 'transition', ..., message: { text: `Moved to ${stage}` } }) Defensive patterns
Strategy: validation
Validate before calling
function assertTransitionMessage(d) {
const m = (d as any)?.message;
if (m !== undefined && (m === null || typeof m !== 'object' || Array.isArray(m))) {
throw new TypeError('transition decision message must be { text, role? }');
}
} Type guard
const isTransitionMessage = (v): v is { text: string; role?: string } =>
typeof v === 'object' && v !== null && !Array.isArray(v) && typeof (v as any).text === 'string'; Try / catch
try {
return decision(d);
} catch (e) {
if (e instanceof FactoryRuleValidationError && e.message === 'Factory transition message must be an object.') {
logger.error('Bad transition message shape', { message: d.message });
return decision({ ...d, message: typeof d.message === 'string' ? { text: d.message } : undefined });
}
throw e;
} Prevention
- Always pass message as { text: string, role?: string }
- Omit the message key instead of passing null
- Convert external SDK message objects to the plain shape first
- Validate decisions with a zod schema before calling decision()
When it happens
Trigger: decision({ type: 'transition', ..., message: 'hello' }) (string), message: ['hello'], message: new Message(...) (class instance), or message: null where null counts as defined-but-not-object.
Common situations: Passing a template string instead of { text: ... }; framework message objects (e.g. from another SDK) passed through unchanged; null from optional chaining where undefined was meant.
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
- Factory rules.github.${event}.onEvent must be a function.
- Factory rules.linear.${event}.onEvent must be a function.
- Factory rule decision must be an object.
- Factory transition reenter must be a boolean.
- CursorSDKAgent resumeData.agentId must be a string when prov
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/cb4c7c1e45d64e55.
Report an issue: GitHub.