mastra-ai/mastra · error · FactoryRuleValidationError
Rule metadata must be an object.
Error message
Rule metadata must be an object.
What it means
The metadata field of an upsertLinkedWorkItem decision must normalize to a plain object of JSON values (or be omitted). If the top-level metadata value is an array, string, number, or boolean, sanitizeMetadata throws because downstream consumers expect a Record<string, FactoryRuleJsonValue>. Additionally the serialized metadata must stay under 16,384 characters ('Rule metadata is too large').
Source
Thrown at mastracode/factory/src/rules/validation.ts:135
throw new FactoryRuleValidationError('Rule metadata contains too many fields.');
}
const sanitized: Record<string, FactoryRuleJsonValue> = {};
for (const [key, entry] of entries) {
const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);
sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)
? '[REDACTED]'
: normalizeFactoryRuleJsonValue(entry, depth + 1, seen);
}
return sanitized;
} finally {
seen.delete(value as object);
}
}
function sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {
if (value === undefined) return undefined;
const sanitized = normalizeFactoryRuleJsonValue(value);
if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');
if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {
throw new FactoryRuleValidationError('Rule metadata is too large.');
}
return sanitized;
}
function validateBoardRules(rules: unknown, label: string): asserts rules is FactoryBoardRules {
if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);
for (const [stage, sources] of Object.entries(rules)) {
enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);
if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);
for (const [source, leaf] of Object.entries(sources)) {
enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);
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.`);View on GitHub (pinned to 75dd419e61)
Solutions
- Wrap the value in an object: metadata: { items: [...] } instead of metadata: [...].
- Omit the metadata field entirely if there is nothing to attach.
- If JSON.stringify(value).length > 16384, summarize, truncate lists, or move bulk data out of metadata.
- Validate the shape (plain object) and approximate serialized size before emitting the decision.
Example fix
// before
metadata: ['build-log', 'coverage']
// after
metadata: { labels: ['build-log', 'coverage'] } Defensive patterns
Strategy: validation
Validate before calling
const safeMetadata = (m: unknown): Record<string, unknown> | undefined => {
if (m === undefined) return undefined;
if (typeof m !== 'object' || m === null || Array.isArray(m)) throw new TypeError('metadata must be an object');
if (JSON.stringify(m).length > 16384) throw new RangeError('metadata exceeds 16 KiB');
return m as Record<string, unknown>;
}; Type guard
const isMetadataShape = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
Try / catch
try {
emit({ type: 'upsertLinkedWorkItem', metadata, /* ... */ });
} catch (e) {
if (e instanceof FactoryRuleValidationError && /metadata (must be an object|is too large)/.test(e.message)) {
emit({ type: 'upsertLinkedWorkItem', metadata: { summary: JSON.stringify(metadata).slice(0, 2000) } });
} else throw e;
} Prevention
- Always pass metadata as a top-level object; wrap lists in a named key.
- Omit the metadata key when there is nothing to attach.
- Check JSON.stringify(metadata).length <= 16384 before emitting decisions.
- Centralize metadata construction in one helper that enforces both shape and size.
When it happens
Trigger: Passing metadata as an array (e.g. metadata: ['a','b']), a bare string, or a number instead of an object; or passing an object whose JSON.stringify exceeds 16 KiB after sanitization/redaction.
Common situations: Confusing metadata (key-value record) with a payload/list field, storing a whole report blob in metadata and hitting the 16 KiB cap, LLM-generated decisions putting a list in metadata.
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
- Rule metadata must contain finite numbers.
- Rule metadata is not bounded JSON.
- Rule metadata must not contain cycles.
- Rule metadata contains too many entries.
- Rule metadata must use plain objects.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/2aedd98a03dc04f4.
Report an issue: GitHub.