mastra-ai/mastra · error
${path} must not contain cycles.
Error message
${path} must not contain cycles. What it means
To detect self-referential structures, normalizeJsonValue() tracks every object/array it enters in a `seen` Set. Re-encountering an object already on the current traversal means a reference cycle, which JSON.stringify cannot serialize, so the builder throws this TypeError with the cycle's path.
Source
Thrown at packages/core/src/workflows/builder/index.ts:134
'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 and
// nullable, so strict-provider models are forced to emit `null` for fields theyView on GitHub (pinned to 75dd419e61)
Solutions
- Break cycles by removing back-pointers before passing the data (store ids/keys instead of object references)
- Use a safe deep-clone that throws/handles cycles, or serialize deliberately with a replacer that skips circular refs
- Restructure the data to a flat, reference-by-id shape suited to JSON
- Use the reported `path` to identify which property creates the loop
Example fix
// before
const child = { parent }; parent.child = child; // cycle
builder.withParams(parent);
// after
const child = { parentId: parent.id }; // reference by id, no cycle
builder.withParams({ ...parent, child }); Defensive patterns
Strategy: validation
Validate before calling
function assertAcyclic(obj, seen = new WeakSet(), path = 'input') {
if (obj && typeof obj === 'object') {
if (seen.has(obj)) throw new Error(`cycle at ${path}`);
seen.add(obj);
for (const [k, v] of Object.entries(obj)) assertAcyclic(v, seen, `${path}.${k}`);
}
}
assertAcyclic(params); Try / catch
try {
builder.withParams(raw);
} catch (e) {
if (e instanceof TypeError && e.message.includes('must not contain cycles')) {
logger.error('Circular reference at', e.message);
} else throw e;
} Prevention
- Model parent/child links by id, not object back-references
- Run an assertAcyclic check on generated config objects in tests
- Avoid passing shared/cached object graphs directly as builder input
When it happens
Trigger: Passing an object graph containing circular references (obj.self = obj; parent.child.parent = parent; circular linked structures) into workflow builder input.
Common situations: Parent/child tree nodes with back-pointers; objects augmented with references to their container; deeply shared state from caches or DI containers reused as config values.
Related errors
- ${path} must contain only finite numbers.
- ${path} must be JSON-safe.
- 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/f4d08a89e67ce21f.
Report an issue: GitHub.