mastra-ai/mastra · error
${path} must be JSON-safe.
Error message
${path} must be JSON-safe. What it means
normalizeJsonValue() rejects anything that isn't null, string, boolean, finite number, plain object, or array. Values like undefined, functions, Symbols, class instances, Dates, or Maps hit the `typeof value !== 'object'` / non-plain-prototype checks and throw this TypeError with the offending path. The builder enforces strict JSON-safety for all input trees.
Source
Thrown at packages/core/src/workflows/builder/index.ts:133
'parallel',
'foreach',
'sleep',
'sleepUntil',
'conditional',
'loop',
] as const;
export type WorkflowBuilderSupportedStepType = (typeof WORKFLOW_BUILDER_SUPPORTED_STEP_TYPES)[number];
export { WORKFLOW_BUILDER_AUTHORING_CONSTRAINTS, WORKFLOW_BUILDER_AUTHORING_PLAYBOOK } from './authoring-playbook';
function normalizeJsonValue(value: unknown, path: string, seen: Set<object>): WorkflowBuilderJsonValue {
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
if (typeof value === 'number') {
if (!Number.isFinite(value)) throw new TypeError(`${path} must contain only finite numbers.`);
return value;
}
if (typeof value !== 'object') throw new TypeError(`${path} must be JSON-safe.`);
if (seen.has(value)) throw new TypeError(`${path} must not contain cycles.`);
seen.add(value);
try {
if (Array.isArray(value)) return value.map((item, index) => normalizeJsonValue(item, `${path}.${index}`, seen));
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
throw new TypeError(`${path} must contain only plain objects.`);
}
const normalized: WorkflowBuilderJsonObject = {};
for (const [key, item] of Object.entries(value)) {
if (item !== undefined) normalized[key] = normalizeJsonValue(item, `${path}.${key}`, seen);
}
return normalized;
} finally {
seen.delete(value);
}
}
// OpenAI strict-schema compatibility makes every optional property required andView on GitHub (pinned to 75dd419e61)
Solutions
- Convert Dates to .toISOString(), Maps/Sets to arrays/records, and class instances to plain objects (structuredClone or toObject)
- Filter out undefined fields before passing the object (e.g. JSON.parse(JSON.stringify()) for trusted data, or an explicit omit-undefined pass)
- Replace functions/Symbols/BigInts with serializable representations (string names, Number())
- Use the reported `path` to locate the exact offending value
Example fix
// before
builder.withParams({ createdAt: new Date(), onUpdate: () => {} });
// after
builder.withParams({ createdAt: new Date().toISOString() }); Defensive patterns
Strategy: validation
Validate before calling
const isJsonSafe = (v) => v === null || ['string','boolean','number'].includes(typeof v) || (typeof v === 'object' && !Array.isArray(v) === false || v.constructor === Object);
function deepSanitize(o) { return JSON.parse(JSON.stringify(o, (k, v) => v === undefined ? null : v)); } Type guard
function isPlainObject(v) { return typeof v === 'object' && v !== null && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null); } Try / catch
try {
builder.withParams(raw);
} catch (e) {
if (e instanceof TypeError && e.message.includes('must be JSON-safe')) {
builder.withParams(deepSanitize(raw));
} else throw e;
} Prevention
- Convert Dates to ISO strings, Maps/Sets to plain structures
- Never pass functions, Symbols, or BigInts in builder input
- Strip undefined fields before submission
- Prefer explicit mapping to plain objects over passing ORM/class instances
When it happens
Trigger: Passing undefined, a function, a Symbol, a BigInt, or a non-plain object (Date, Map, Set, class instance) anywhere inside data handed to workflow builder APIs.
Common situations: Embedding a Date directly instead of ISO string; passing class instances from ORM results; forgetting that optional fields left as `undefined` are not JSON-safe; including callback functions in config objects.
Related errors
- ${path} must contain only finite numbers.
- ${path} must not contain cycles.
- Rule metadata must contain finite numbers.
- Rule metadata is not bounded JSON.
- Rule metadata must not contain cycles.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/0a34577c7c1d93f1.
Report an issue: GitHub.