What this error means
In OpenAPI 3.0, a $ref follows JSON Reference semantics: the whole object containing the $ref is replaced by whatever it points at. Anything else in that object, a description, an example, a nullable, is discarded during resolution. Nothing crashes. Your annotation just never shows up in rendered documentation or generated code, which is worse than an error because nobody notices.
Spectral's no-$ref-siblings rule exists to turn that silent loss into a visible message. It fires once per sibling property, so a single $ref with a description and an example beside it produces two occurrences.
Why Spectral emits it
The pattern feels natural to write, which is exactly the problem. You reference a shared schema and want to say what the field means in this context:
components:
schemas:
User:
type: object
properties:
id:
type: string
Account:
type: object
properties:
owner:
$ref: '#/components/schemas/User'
description: The user who owns this account # ignoredcomponents:
schemas:
User:
type: object
properties:
id:
type: string
Account:
type: object
properties:
owner:
allOf:
- $ref: '#/components/schemas/User'
description: The user who owns this account # keptThe allOf wrapper makes the outer object a schema in its own right, so its description survives, while the single-element allOf composes in the referenced User unchanged. If the annotation belongs to the schema everywhere it is used, skip the wrapper and move the description into User itself.
The OpenAPI 3.1 difference
OpenAPI 3.1 adopts JSON Schema 2020-12, where $ref inside a schema may carry sibling keywords and they apply alongside the reference. That is why Spectral scopes no-$ref-siblings to oas2 and oas3_0 formats only. Upgrading the document to openapi: 3.1.0 makes the broken example above legal, but check that every consumer of your spec actually supports 3.1 before using the version bump as the fix.
How to catch it earlier
Because nothing fails at runtime, this mistake survives until someone asks why a field description is missing from the docs. Run the free OpenAPI linter over your spec to surface every dropped sibling at once, and use the OpenAPI validator to confirm the allOf rewrite still resolves cleanly. In CI, npx @stoplight/spectral-cli lint openapi.yaml enforces the rule on every pull request.
Frequently asked questions
Why are siblings of $ref ignored in OpenAPI 3.0?
JSON Reference semantics: the object holding the $ref is replaced entirely by the target during resolution, so sibling keys never make it into the resolved document.
Does OpenAPI 3.1 allow this?
Inside schemas, yes: JSON Schema 2020-12 lets $ref coexist with keywords like description. Outside schemas, Reference Objects in 3.1 permit only summary and description as siblings. Spectral accordingly skips this rule for 3.1 documents.
How do I attach a description to a $ref in 3.0?
Wrap the reference in a single-element allOf and put the description on the outer schema, or move the text into the referenced schema when it applies globally.