{"record":{"id":"f4d08a89e67ce21f","repo":"mastra-ai/mastra","slug":"path-must-not-contain-cycles","errorCode":null,"errorMessage":"${path} must not contain cycles.","messagePattern":"(.+?) must not contain cycles\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/workflows/builder/index.ts","lineNumber":134,"sourceCode":"  'foreach',\n  'sleep',\n  'sleepUntil',\n  'conditional',\n  'loop',\n] as const;\n\nexport type WorkflowBuilderSupportedStepType = (typeof WORKFLOW_BUILDER_SUPPORTED_STEP_TYPES)[number];\n\nexport { WORKFLOW_BUILDER_AUTHORING_CONSTRAINTS, WORKFLOW_BUILDER_AUTHORING_PLAYBOOK } from './authoring-playbook';\n\nfunction normalizeJsonValue(value: unknown, path: string, seen: Set<object>): WorkflowBuilderJsonValue {\n  if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) throw new TypeError(`${path} must contain only finite numbers.`);\n    return value;\n  }\n  if (typeof value !== 'object') throw new TypeError(`${path} must be JSON-safe.`);\n  if (seen.has(value)) throw new TypeError(`${path} must not contain cycles.`);\n  seen.add(value);\n  try {\n    if (Array.isArray(value)) return value.map((item, index) => normalizeJsonValue(item, `${path}.${index}`, seen));\n    if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\n      throw new TypeError(`${path} must contain only plain objects.`);\n    }\n    const normalized: WorkflowBuilderJsonObject = {};\n    for (const [key, item] of Object.entries(value)) {\n      if (item !== undefined) normalized[key] = normalizeJsonValue(item, `${path}.${key}`, seen);\n    }\n    return normalized;\n  } finally {\n    seen.delete(value);\n  }\n}\n\n// OpenAI strict-schema compatibility makes every optional property required and\n// nullable, so strict-provider models are forced to emit `null` for fields they","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workflows/builder/index.ts#L116-L152","documentation":"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.","triggerScenarios":"Passing an object graph containing circular references (obj.self = obj; parent.child.parent = parent; circular linked structures) into workflow builder input.","commonSituations":"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.","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"],"exampleFix":"// before\nconst child = { parent }; parent.child = child; // cycle\nbuilder.withParams(parent);\n// after\nconst child = { parentId: parent.id }; // reference by id, no cycle\nbuilder.withParams({ ...parent, child });","handlingStrategy":"validation","validationCode":"function assertAcyclic(obj, seen = new WeakSet(), path = 'input') {\n  if (obj && typeof obj === 'object') {\n    if (seen.has(obj)) throw new Error(`cycle at ${path}`);\n    seen.add(obj);\n    for (const [k, v] of Object.entries(obj)) assertAcyclic(v, seen, `${path}.${k}`);\n  }\n}\nassertAcyclic(params);","typeGuard":null,"tryCatchPattern":"try {\n  builder.withParams(raw);\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('must not contain cycles')) {\n    logger.error('Circular reference at', e.message);\n  } else throw e;\n}","preventionTips":["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"],"tags":["validation","json","circular-reference","workflow-builder"],"backgroundTag":"circular-reference-detected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}