mastra-ai/mastra · error · FactoryRuleValidationError
A rejection cannot be persisted with commit decisions.
Error message
A rejection cannot be persisted with commit decisions.
What it means
A batch passed to validateFactoryRuleDecisions is either a pure commit (create/update/link/message/skill/notification decisions) or a single reject — never both. If any validated decision has type 'reject' the whole batch is thrown out, because persisting a rejection alongside commit decisions would make the transaction semantics ambiguous.
Source
Thrown at mastracode/factory/src/rules/validation.ts:366
title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),
...(body ? { body } : {}),
...(level ? { level } : {}),
};
}
default:
throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');
}
}
export function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {
if (values.length > MAX_JSON_COLLECTION_SIZE) {
throw new FactoryRuleValidationError('Factory rule produced too many decisions.');
}
const decisions: FactoryCommitDecision[] = [];
for (const value of values) {
const decision = validateFactoryRuleDecision(value, causalDepth);
if (decision.type === 'reject') {
throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');
}
decisions.push(decision);
}
const keys = decisions.map(decision => decision.idempotencyKey);
if (new Set(keys).size !== keys.length) {
throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');
}
return decisions;
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Return either [reject] alone, or only commit decisions — early-return the reject instead of appending it.
- Restructure the rule so a failing condition short-circuits to a single reject decision.
- Convert the reject into an error/exception path (or a notification decision) if partial commits are intended.
Example fix
// before
const out = [commitDecision()];
if (bad) out.push({ type: 'reject', ... });
return out;
// after
if (bad) return [{ type: 'reject', ... }];
return [commitDecision()]; Defensive patterns
Strategy: validation
Validate before calling
function checkNoRejectMix(decisions) {
const hasReject = decisions.some(d => d.type === 'reject');
if (hasReject && decisions.length > 1) {
throw new Error('reject must be the only decision in a batch');
}
} Type guard
function isPureRejectBatch(ds: Array<{ type: string }>): ds is [{ type: 'reject' }] {
return ds.length === 1 && ds[0].type === 'reject';
} Try / catch
try {
return validateFactoryRuleDecisions(decisions);
} catch (e) {
if (e instanceof FactoryRuleValidationError && /rejection cannot be persisted/.test(e.message)) {
const reject = decisions.find(d => d.type === 'reject');
return validateFactoryRuleDecisions(reject ? [reject] : []); // fail the batch outright
}
throw e;
} Prevention
- Short-circuit: return the reject decision immediately instead of pushing into a shared array.
- Separate reject paths from commit paths in rule code structure.
- Write a rule unit test asserting reject batches contain exactly one decision.
When it happens
Trigger: A rule returning an array like [{type:'reject',...},{type:'message',...}] from evaluate, ingestToolResult, transition, or the other listed callers — i.e. any mix containing a reject decision with at least one commit decision.
Common situations: Rules that conditionally push a reject into a shared decisions array while other branches push commits; aggregating results from multiple sub-checks where one fails with reject while others succeeded.
Related errors
- Factory rule version is required.
- ${label} contains an unsupported field.
- Rule metadata is too large.
- ${label} must be an object.
- ${label}.${stage} must be an object.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/f94292a9744a9dff.
Report an issue: GitHub.