mastra-ai/mastra · error · FactoryRuleValidationError
Rule metadata must contain finite numbers.
Error message
Rule metadata must contain finite numbers.
What it means
Rule metadata (the metadata field of an upsertLinkedWorkItem decision) must be JSON-serializable with finite numbers only. normalizeFactoryRuleJsonValue rejects NaN, Infinity, and -Infinity because they do not survive JSON.stringify (they become null) and indicate numeric bugs. The library throws this before persisting metadata.
Source
Thrown at mastracode/factory/src/rules/validation.ts:99
if (value === undefined) return undefined;
return boundedString(value, label, max);
}
function enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {
if (typeof value !== 'string' || !allowed.includes(value as T)) {
throw new FactoryRuleValidationError(`${label} is invalid.`);
}
return value as T;
}
export function normalizeFactoryRuleJsonValue(
value: unknown,
depth = 0,
seen = new Set<object>(),
): FactoryRuleJsonValue {
if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;
if (typeof value === 'number') {
if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');
return value;
}
if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {
throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');
}
if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');
seen.add(value as object);
try {
if (Array.isArray(value)) {
if (value.length > MAX_JSON_COLLECTION_SIZE) {
throw new FactoryRuleValidationError('Rule metadata contains too many entries.');
}
return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));
}
if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');
const entries = Object.entries(value);
if (entries.length > MAX_JSON_COLLECTION_SIZE) {
throw new FactoryRuleValidationError('Rule metadata contains too many fields.');View on GitHub (pinned to 75dd419e61)
Solutions
- Check Number.isFinite(value) before placing computed numbers into metadata, and substitute a fallback (e.g. 0 or null).
- Replace NaN/Infinity with null (null is allowed) or a sentinel string.
- Fix the upstream arithmetic (guard division by zero, validate parsed input).
- Sanitize metadata through a JSON round-trip JSON.parse(JSON.stringify(value)) and inspect what becomes null.
Example fix
// before
metadata: { coverage: passed / total }
// after
metadata: { coverage: total > 0 ? passed / total : null } Defensive patterns
Strategy: validation
Validate before calling
const assertFiniteNumbers = (obj: unknown, depth = 0): void => {
if (typeof obj === 'number' && !Number.isFinite(obj)) throw new RangeError('metadata contains non-finite number');
if (obj && typeof obj === 'object') for (const v of Object.values(obj)) assertFiniteNumbers(v, depth + 1);
};
assertFiniteNumbers(metadata); Type guard
const isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);
Try / catch
try {
decisions.push({ type: 'upsertLinkedWorkItem', metadata, /* ... */ });
} catch (e) {
if (e instanceof FactoryRuleValidationError && e.message.includes('finite numbers')) {
metadata = JSON.parse(JSON.stringify(metadata, (k, v) => (typeof v === 'number' && !Number.isFinite(v) ? null : v)));
} else throw e;
} Prevention
- Guard division by zero and Number()/parseInt results before storing them in metadata.
- Use null (allowed) instead of NaN/Infinity for missing numeric values.
- Run metadata through Number.isFinite checks in a shared sanitize helper.
- Prefer JSON.parse(JSON.stringify(...)) round-trip in tests to catch non-JSON numbers early.
When it happens
Trigger: Passing metadata with values like { attempts: NaN } or { ratio: 1/0 }, often the result of division by zero, parseInt on a bad string, or undefined arithmetic in the rule code producing NaN.
Common situations: Computing durations/ratios in rule handlers where a denominator is 0, parsing malformed numeric input with Number() returning NaN, spreading objects where a field was lost and NaN results.
Related errors
- Rule metadata is not bounded JSON.
- Rule metadata must not contain cycles.
- Rule metadata contains too many entries.
- Rule metadata must use plain objects.
- Rule metadata contains too many fields.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/6af2a06b1465b8dc.
Report an issue: GitHub.