{"id":"96ac7ae76834e166","repo":"colinhacks/zod","slug":"cycle-detected-seen-cycle-join-root","errorCode":null,"errorMessage":"Cycle detected: #/${seen.cycle?.join(\"/\")}/<root>\n\nSet the `cycles` parameter to `\"ref\"` to resolve cyclical schemas with defs.","messagePattern":"Cycle detected: #/(.+?)/<root>\n\nSet the `cycles` parameter to `\"ref\"` to resolve cyclical schemas with defs\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/core/to-json-schema.ts","lineNumber":306,"sourceCode":"    // defId won't be set if the schema is a reference to an external schema\n    // or if the schema is the root schema\n    if (defId) seen.defId = defId;\n    // wipe away all properties except $ref\n    const schema = seen.schema;\n    for (const key in schema) {\n      delete schema[key];\n    }\n    schema.$ref = ref;\n  };\n\n  // throw on cycles\n\n  // break cycles\n  if (ctx.cycles === \"throw\") {\n    for (const entry of ctx.seen.entries()) {\n      const seen = entry[1];\n      if (seen.cycle) {\n        throw new Error(\n          \"Cycle detected: \" +\n            `#/${seen.cycle?.join(\"/\")}/<root>` +\n            '\\n\\nSet the `cycles` parameter to `\"ref\"` to resolve cyclical schemas with defs.'\n        );\n      }\n    }\n  }\n\n  // extract schemas into $defs\n  for (const entry of ctx.seen.entries()) {\n    const seen = entry[1];\n\n    // convert root schema to # $ref\n    if (schema === entry[0]) {\n      extractToDef(entry); // this has special handling for the root schema\n      continue;\n    }\n","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/colinhacks/zod/blob/912f0f51b0ced654d0069741e7160834dca742ee/packages/zod/src/v4/core/to-json-schema.ts#L288-L324","documentation":"Thrown during `z.toJSONSchema()` when the schema graph contains a cycle (a schema that references itself, directly or indirectly) and the `cycles` option is set to `\"throw\"`. Note Zod v4 defaults `cycles` to `\"ref\"` (which resolves cycles with `$defs`/`$ref`), so this error only fires when you explicitly pass `{ cycles: 'throw' }`. The message reports the path of the detected cycle.","triggerScenarios":"Defining a recursive schema (e.g. a tree/node that contains itself via `z.lazy(...)`) and calling `z.toJSONSchema(schema, { cycles: 'throw' })`. Mutually recursive schemas also trigger it.","commonSituations":"Explicitly opting into cycle detection to catch accidental recursion; converting a schema tree that was expected to be acyclic but contains a `z.lazy` self-reference.","solutions":["Pass `{ cycles: 'ref' }` (the default) to emit cyclic schemas using `$defs` and `$ref` pointers.","If the cycle is unintentional, remove the self-reference (e.g. the `z.lazy(() => schema)` pointing back at the root).","For draft-07 or openapi-3.0 targets, confirm the emitted `$ref`/`definitions` shape is acceptable to your downstream consumer."],"exampleFix":"// before\nconst Tree = z.lazy(() => z.object({ value: z.number(), children: z.array(Tree) }));\nz.toJSONSchema(Tree, { cycles: 'throw' }); // throws\n\n// after\nz.toJSONSchema(Tree, { cycles: 'ref' }); // emits $defs + $ref","handlingStrategy":"validation","validationCode":"// Default is cycles:'ref'; only throw is explicit. To avoid surprises:\nfunction convertSafely(schema) {\n  return z.toJSONSchema(schema, { cycles: 'ref' }); // always resolve cycles\n}","typeGuard":null,"tryCatchPattern":"try {\n  z.toJSONSchema(schema, { cycles: 'throw' });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Cycle detected')) {\n    // retry with cycles: 'ref' to emit $defs/$ref\n    z.toJSONSchema(schema, { cycles: 'ref' });\n  }\n}","preventionTips":["Prefer the default cycles:'ref' for any potentially recursive schema.","Only use cycles:'throw' in tests asserting acyclicity.","Document recursive schemas (z.lazy) as requiring cycles:'ref' when converting."],"tags":["json-schema","conversion","recursion","cycles"],"analyzedSha":"912f0f51b0ced654d0069741e7160834dca742ee","analyzedAt":"2026-08-03T17:41:55.908Z","schemaVersion":2}