Skip to content

bundle drops sibling keywords when an external schema file has a root-level $ref #2964

Description

@adamaltman

Describe the bug

When an external schema file uses OpenAPI 3.1 root-level $ref with sibling keywords (type, title, properties, required, etc.), redocly bundle resolves that file to only the $ref target and discards the siblings.

References to that schema are then rewritten to the target schema. Intermediate schemas effectively disappear from the useful bundled model (or remain as a pure $ref alias with no siblings).

redocly lint does not report a problem on the source; the loss happens at bundle time.

OpenAPI 3.1 context (Reference Object vs Schema Object)

This report is specifically about Schema Objects (data models), not Reference Objects elsewhere in the document.

In OpenAPI 3.1+:

  • Reference Objects outside Schema Objects (for example path item, parameter, response, or request body refs) are still Reference Objects. Sibling keywords next to $ref are restricted (notably summary and description).
  • Schema Objects adopt JSON Schema Draft 2020-12. In that model, $ref may appear alongside other schema keywords, and those siblings are applied together with the referenced schema (composition), not ignored.

So a schema like:

title: Bad request
type: object
$ref: ./BaseProblem.yaml
properties:
  status:
    type: integer
required:
  - status

is a valid OAS 3.1 Schema Object / JSON Schema 2020-12 schema. Bundling should preserve that composition, not treat the root $ref as a full replacement the way a non-schema Reference Object might be inlined.

Repro

# openapi.yaml
openapi: 3.1.0
info:
  title: file $ref sibling repro
  version: 1.0.0
paths:
  /demo:
    get:
      operationId: getDemo
      responses:
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: ./schemas/BadRequest.yaml
components:
  schemas:
    BadRequest:
      $ref: ./schemas/BadRequest.yaml
    BaseProblem:
      $ref: ./schemas/BaseProblem.yaml
# schemas/BaseProblem.yaml
type: object
properties:
  title:
    type: string
# schemas/BadRequest.yaml
title: Bad request
type: object
$ref: ./BaseProblem.yaml
properties:
  status:
    type: integer
    minimum: 400
    maximum: 400
required:
  - status
npx @redocly/cli@2.38.0 bundle openapi.yaml --ext json
# also reproduced with 2.39.0 and 2.40.0

Actual

"BadRequest": { "$ref": "#/components/schemas/BaseProblem" }

Path/response 400 schema also points at BaseProblem. Sibling keywords from BadRequest.yaml (title, type, properties.status, required) are gone.

Expected

Bundled BadRequest keeps sibling keywords composed with BaseProblem (or an equivalent merged/allOf form), and references keep pointing at BadRequest.

Control

The same API expressed with allOf instead of root $ref + siblings bundles correctly (BadRequest retained; refs point at BadRequest).

Property-level $ref + siblings (for example writeOnly next to a property $ref) appear to bundle correctly in our larger spec. The failure we hit is specific to root-level $ref + siblings on an external schema file.

Independent validation (not Redocly)

We validated the same $ref + siblings composition with popular JSON Schema Draft 2020-12 implementations. Sibling keywords are applied, not ignored:

Schema under test (equivalent form):

  • $ref to BaseProblem
  • sibling properties.status (minimum/maximum 400)
  • sibling required: ["status"]
Instance Ajv (2020-12) @hyperjump/json-schema Python jsonschema (Draft202012Validator)
{ "title": "Bad request", "status": 400 } PASS PASS PASS
{ "title": "Bad request" } (missing status) FAIL FAIL FAIL
{ "title": "Bad request", "status": 500 } FAIL FAIL FAIL

Ajv also compiles the schema successfully against the Draft 2020-12 meta-schema.

If the schema is collapsed to Redocly’s bundled form ({ "$ref": "..." } only), Ajv accepts the missing-status instance — i.e. the sibling constraints are lost. That matches the buggy bundle output and shows the bundler is dropping JSON Schema semantics that other validators enforce.

Separately, @apidevtools/swagger-parser accepts the OpenAPI 3.1 document; after dereference, BadRequest still retains properties.status.

Environment

  • @redocly/cli 2.38.0, 2.39.0, 2.40.0
  • OpenAPI 3.1.0 / 3.1.1

Notes

Possibly related to sibling traversal work in #2768 / #2772 / #1907 / #2362, but those focus on resolving refs inside siblings. Here sibling keywords on an external schema file root are dropped entirely when that root $ref is resolved.

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