OpenAPI errors

Fix YAML anchors and merge keys rejected in OpenAPI specs

A YAML anchor and alias (&name / *name) is fine in an OpenAPI spec — it's resolved before any tool sees the document. A merge key (<<: *name), the shorthand people reach for to avoid repeating shared properties across schemas, is a different story: it's not part of core YAML, and when it collides with a key already in the mapping it loses silently instead of erroring. Reproduced two ways on 2026-09-21 — a hard validation failure in one tool, a silent property drop in another — against the same spec.

What does this actually look like?

Start with a shared Timestamped fragment, anchored once and merged into two schemas:

components:
  schemas:
    Timestamped: &timestamped
      type: object
      properties:
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    Widget:
      <<: *timestamped
      type: object
      properties:
        id:
          type: string
        name:
          type: string

Running this exact spec through swagger-cli validate v4.0.4 produces a real, unedited failure:

Swagger schema validation failed.
  #/components/schemas/Widget must NOT have additional properties
  #/components/schemas/Widget must have required property '$ref'
  #/components/schemas/Widget must match exactly one schema in oneOf

swagger-cli's parser doesn't expand the merge key at all — it hands the JSON Schema validator a literal << property on Widget, which isn't a keyword the Schema Object recognizes, so the whole schema fails against every branch of OpenAPI's oneOf for "what a Schema Object can be."

That's the loud failure. The quiet one is worse. @redocly/cli v2.53.3 validates and bundles the same spec without complaint — its YAML parser does expand the merge key — but look at what redocly bundle actually writes for Widget:

Widget:
  type: object
  properties:
    id:
      type: string
    name:
      type: string

createdAt and updatedAt are gone. No error, no warning — redocly lint only flags Timestamped as an unused component, which is a hint something's off but not a description of what went wrong. Feeding the same unbundled spec straight into OpenAPI Generator v7.25.0 (-g typescript-fetch) reproduces the exact same loss in the generated model:

export interface Widget {
    id?: string;
    name?: string;
}

Two independent tools, two different YAML parsers, the same missing fields.

Why does the merge key drop properties instead of erroring or merging deeply?

Because of how the merge key was ever defined. It comes from a YAML 1.1 type proposal, never core YAML, and its own spec says: "each of its key/value pairs is inserted into the current mapping, unless the key already exists in it." That's a shallow, key-by-key merge, and explicit keys always win over merged ones.

In the Widget example, the anchor and the explicit mapping both declare properties — so properties: { id, name } written directly on Widget wins outright, and the entire properties block from the anchor (createdAt, updatedAt and everything else it might carry) is discarded, not merged field-by-field. This is correct behavior per the merge-key spec; it just isn't what most people expect a "merge" to do, and OpenAPI's JSON Schema-based tooling has no way to warn you about it because from a schema-validity standpoint nothing is wrong — you just got a smaller, perfectly valid Widget.

How do you fix it?

Don't rely on <<: colliding with a sibling key. Either avoid the collision, or use allOf — the OpenAPI-native composition keyword that's built for exactly this and has no key-shadowing behavior to trip over:

# Before — merge key collides with an explicit `properties` key and loses silently
Widget:
  <<: *timestamped
  type: object
  properties:
    id:
      type: string
    name:
      type: string

# After — allOf composes without any key collision
Widget:
  allOf:
    - $ref: '#/components/schemas/Timestamped'
    - type: object
      properties:
        id:
          type: string
        name:
          type: string

Regenerating with the same OpenAPI Generator version and flags now produces a Widget with all four properties present, and swagger-cli validate passes cleanly — because allOf with $ref is a construct every OpenAPI-aware tool actually understands, not a YAML-level trick those tools happen to parse (or not parse) as a side effect.

Tool reaction table

Tool (version) What it does with <<: colliding on properties
swagger-cli validate v4.0.4 Hard failure — << treated as a literal, invalid property
@redocly/cli lint/bundle v2.53.3 Passes; merge resolved but shadowed properties silently dropped
OpenAPI Generator v7.25.0 Same silent drop carried into the generated model
allOf + $ref (the fix) Validates and generates correctly in all three

How to catch this before it ships

  1. Treat <<: as fragile the moment it sits next to any sibling key with the same name (properties, required, type) — that's exactly when YAML merge semantics diverge from what you'd expect a "merge" to do. Composition bugs like this are a broader category — see allOf composition producing broken generated models and additionalProperties: false breaking allOf payloads for related silent-composition failures.
  2. Prefer allOf with $ref for any schema composition in OpenAPI. It's the spec's own mechanism, every generator resolves it the same way, and it can't silently drop a branch.
  3. If you inherit specs that already use YAML anchors and merge keys, bundle the file (redocly bundle) and diff the resolved output against what you expect — a shrinking properties list is the tell.
  4. Run the spec through the free in-browser OpenAPI validator before generation — it resolves the document the way downstream tooling will, so a dropped property shows up in the preview instead of in a customer's generated SDK. For general spec hygiene beyond this one issue, see OpenAPI best practices for SDK-friendly specs.

If your spec still carries hand-written YAML anchors from an earlier draft — a common leftover once a spec grows past a handful of schemas — Sourced's hosted docs and SDK pipeline bundles and previews the resolved schema before anything ships, so a silently dropped field is visible before publish, not after a support ticket. Start a free report.

FAQ

Are YAML anchors and aliases themselves ever a problem in OpenAPI?

No. A plain anchor/alias pair (&name / *name) is resolved by any conformant YAML parser before the document even looks like OpenAPI — every tool sees the same expanded structure. The merge key (<<:) is the part that's non-standard and inconsistently supported, and it's also the only one of the two with silent-data-loss behavior when keys collide.

Why did swagger-cli fail hard while redocly didn't fail at all?

They use different YAML parsing paths. swagger-cli's validator doesn't expand <<: as a merge key, so it's left in the document as a literal (and invalid) property name, which JSON Schema validation correctly rejects. Redocly's parser does expand it, so the document becomes schema-valid — but valid isn't the same as correct, and the shadowed properties are still gone.

Does using allOf instead of a merge key hurt readability for shared fields across many schemas?

Not meaningfully — $ref to a shared fragment inside allOf reads the same as a merge key at a glance, and it composes the same way in every tool. If you're sharing the same fragment across a dozen schemas, that's a sign to name it clearly (like Timestamped above) and reference it with allOf, not to lean harder on YAML-level tricks.

Can I keep the merge key if I just avoid naming any sibling key the same as one inside the anchor?

Technically yes — if Widget declared no properties key of its own, the merged properties from Timestamped would come through untouched. But that's fragile: the moment someone adds a property directly to Widget later, the merge silently breaks again with no warning from any validator. allOf doesn't have this failure mode at all.

Does this affect OpenAPI 3.0 and 3.1 differently?

No — this is a YAML-parsing behavior, not an OpenAPI version difference. Both 3.0.x and 3.1.x specs are plain YAML (or JSON) documents, and the merge key's shallow-merge, key-collision-loses behavior is identical regardless of which OpenAPI version the document declares.

Is there a linter rule that catches a merge key shadowing a sibling property?

Not in Redocly's default ruleset as tested here — it flagged the anchor's source schema as "unused" (a no-unused-components warning) but didn't flag the missing properties on Widget itself, since from a schema-validity standpoint there's nothing to flag. Bundling the spec and diffing the resolved output is currently the most reliable way to catch it.