colinhacks/zod · error · Error
Circular reference not resolved: ${refPath}
Error message
Circular reference not resolved: ${refPath} What it means
Thrown inside the z.lazy() thunk emitted for a circular `$ref`. The converter defers recursive refs by returning a lazy schema that reads `ctx.refs.get(refPath)` on demand; if, at evaluation time, the ref was never populated (e.g. the cycle could not be resolved through `$defs`), the thunk throws.
Source
Thrown at packages/zod/src/v4/classic/from-json-schema.ts:179
if (schema.if !== undefined || schema.then !== undefined || schema.else !== undefined) {
throw new Error("Conditional schemas (if/then/else) are not supported");
}
if (schema.dependentSchemas !== undefined || schema.dependentRequired !== undefined) {
throw new Error("dependentSchemas and dependentRequired are not supported");
}
// Handle $ref
if (schema.$ref) {
const refPath = schema.$ref;
if (ctx.refs.has(refPath)) {
return ctx.refs.get(refPath)!;
}
if (ctx.processing.has(refPath)) {
// Circular reference - use lazy
return z.lazy(() => {
if (!ctx.refs.has(refPath)) {
throw new Error(`Circular reference not resolved: ${refPath}`);
}
return ctx.refs.get(refPath)!;
});
}
ctx.processing.add(refPath);
const resolved = resolveRef(refPath, ctx);
const zodSchema = convertSchema(resolved, ctx);
ctx.refs.set(refPath, zodSchema);
ctx.processing.delete(refPath);
return zodSchema;
}
// Handle enum
if (schema.enum !== undefined) {
const enumValues = schema.enum;
// Special case: OpenAPI 3.0 null representation { type: "string", nullable: true, enum: [null] }View on GitHub (pinned to 912f0f51b0)
Solutions
- Ensure the recursive target exists under `$defs`/`definitions` with the exact path used by the `$ref`.
- Verify the `$ref` fragment is in `#/$defs/<Name>` form (or `#/definitions/<Name>` for draft-7).
- Test conversion with sample data that triggers the recursion early to surface the failure during development.
Example fix
// before
{ "$ref": "#/$defs/Node" } // $defs.Node missing or mis-named
// after
{
"$defs": {
"Node": {
"type": "object",
"properties": { "children": { "type": "array", "items": { "$ref": "#/$defs/Node" } } }
}
},
"$ref": "#/$defs/Node"
} Defensive patterns
Strategy: validation
Validate before calling
function assertRecursiveRefsResolve(schema: any) {
const defs = schema.$defs ?? schema.definitions ?? {};
const visit = (n: any, seen = new Set<string>()): boolean => {
if (!n || typeof n !== "object") return true;
if (typeof n.$ref === "string") {
const key = n.$ref.split("/").pop();
if (seen.has(n.$ref)) return key in defs; // cycle: target must exist
if (!defs[key]) return false;
return visit(defs[key], new Set(seen).add(n.$ref));
}
return Object.values(n).every((v) => visit(v, new Set(seen)));
};
if (!visit(schema)) throw new Error("Recursive $ref target missing");
} Type guard
function refTargetExists(schema: any, ref: string): boolean {
const defs = schema.$defs ?? schema.definitions ?? {};
return ref.split("/").pop()! in defs;
} Prevention
- Test converted recursive schemas with sample data that exercises the recursion before production use.
- Keep `$defs` self-contained and verify every recursive target exists.
- Use `#/$defs/Name` consistently for self-reference.
When it happens
Trigger: A self-referential or mutually recursive `$ref` whose target is not present in `$defs`/`definitions` (so resolveRef already failed), or a ref graph the converter's single-pass processing set could not close. Typically surfaces only when the returned Zod schema is actually used to parse data that exercises the recursion.
Common situations: Tree/node schemas with `#/$defs/Node` referencing itself; recursive OpenAPI components where the path is wrong; converting partial schemas whose `$defs` were stripped.
Related errors
- fromJSONSchema input is not valid JSON (possibly cyclic); us
- External $ref is not supported, only local refs (#/...) are
- Reference not found: ${ref}
- not is not supported in Zod (except { not: {} } for never)
- unevaluatedItems is not supported
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/2d21be4087dd5881.json.
Report an issue: GitHub.