Skip to content

CompoundFilter and BetaCompoundFilter use $recursiveRef, which breaks their recursion under OpenAPI 3.1 #585

Open

Description

@simonwhitaker

CompoundFilter refers to itself like this:

CompoundFilter:
  $recursiveAnchor: true
  type: object
  properties:
    filters:
      type: array
      items:
        oneOf:
          - $ref: "#/components/schemas/ComparisonFilter"
          - $recursiveRef: "#"

BetaCompoundFilter does the same.

This doesn't work under either JSON Schema draft:

  • The document declares openapi: 3.1.0, so its schemas use JSON Schema 2020-12. $recursiveRef and $recursiveAnchor belong to draft 2019-09, and 2020-12 dropped them. A 2020-12 validator ignores them, so the second oneOf branch becomes {}, which matches anything. Every valid ComparisonFilter then matches both branches, so oneOf rejects it.
  • Under 2019-09 rules, "#" resolves against the base URI. CompoundFilter has no $id, so "#" points at the root of the OpenAPI document, not at CompoundFilter.

Tools that check strictly reject both schemas, along with every operation that reaches them (21 operations today, including file search and vector store search). #452, #453 and #499 list this among other validator errors.

Suggested fix: use a plain $ref, which OpenAPI 3.1 allows for recursive schemas, and drop $recursiveAnchor:

CompoundFilter:
  type: object
  properties:
    filters:
      type: array
      items:
        oneOf:
          - $ref: "#/components/schemas/ComparisonFilter"
          - $ref: "#/components/schemas/CompoundFilter"

Do the same for BetaCompoundFilter, pointing at #/components/schemas/BetaCompoundFilter.

Activity

  1. added
    bugSomething isn't working
    specificationOpenAPI contract, authored schema, or specification publication defects.
    on Sep 25, 2026
  2. genesiscode2026 commented on Sep 27, 2026

    @genesiscode2026

    Confirming this behavior across modern OpenAPI 3.1 toolchains.

    Because the spec root declares openapi: 3.1.0, parsers evaluate schemas against JSON Schema 2020-12 semantics. In draft 2020-12:

    • $recursiveRef and $recursiveAnchor (from draft 2019-09) were replaced by $dynamicRef and $dynamicAnchor.
    • Parsers strictly adhering to 2020-12 treat $recursiveRef as an unknown extension keyword, breaking recursion in generated SDKs and contract validators.

    To restore standard recursive resolution under OpenAPI 3.1:

    CompoundFilter:
      $dynamicAnchor: filterItem
      type: object
      properties:
        filters:
          type: array
          items:
            oneOf:
              - $ref: "#/components/schemas/ComparisonFilter"
              - $dynamicRef: "#filterItem"

    In our Release Guardian contract checks, we flag this specific draft-version keyword drift during CI before it breaks downstream client generators. If useful, I can share a quick normalization snippet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingspecificationOpenAPI contract, authored schema, or specification publication defects.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions