Skip to content

Recursive z.lazy() schemas produce broken $ref cycles in OpenAPI output #12

@bravehager

Description

@bravehager

We use hono-openapi to generate an OpenAPI spec from Zod v4 schemas. One of our schemas uses z.lazy() for a recursive condition builder:

const ConditionGroup: z.ZodType<any> = z
  .object({
    operator: z.enum(["AND", "OR"]),
    conditions: z.array(z.lazy(() => z.union([Condition, ConditionGroup]))),
  })
  .meta({ ref: "ConditionGroup" });

The generated spec contains $ref: "#/components/schemas/__schema0" and $ref: "#/components/schemas/#" instead of resolving to ConditionGroup. This breaks openapi-typescript codegen and any client that tries to dereference the spec.

I have a fix on my fork: https://github.com/bravehager/standard-openapi/tree/fix/recursive-schema-refs.

I'm opening an issue before a PR because the fix is fairly involved. I needed some help from Claude to even debug what was going on in the conversion pipeline, let alone come up with a working solution. Would love a sanity check before submitting a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions