Skip to content

fix(transform schema): preserve $defs when schema root is a $ref#1642

Merged
benlehrburger-ant merged 1 commit into
mainfrom
benlehrburger/transform-schema-root-ref-defs
Jun 4, 2026
Merged

fix(transform schema): preserve $defs when schema root is a $ref#1642
benlehrburger-ant merged 1 commit into
mainfrom
benlehrburger/transform-schema-root-ref-defs

Conversation

@benlehrburger-ant

@benlehrburger-ant benlehrburger-ant commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

transform_schema() drops a top-level $defs when the schema root is a $ref, leaving a dangling reference that the API rejects.

The early return at the top of transform_schema copies $ref and discards every sibling key — including $defs — before the $defs handling below it can run:

ref = json_schema.pop("$ref", None)
if ref is not None:
    strict_schema["$ref"] = ref
    return strict_schema

That's fine for the recursive case (a nested property that is just {"$ref": ...}), but wrong at the root: $ref with a sibling $defs is valid JSON Schema (draft 2019-09+), and it's exactly what Pydantic v2 emits for RootModel types:

TypeAdapter(RootModel[Tier]).json_schema()
# {"$ref": "#/$defs/Tier", "$defs": {"Tier": {...}}}

Any RootModel output type fed through the strict-mode bindings hits this. Reported by a user; verified present from at least v0.84.0 through v0.105.2.

Repro

from anthropic.lib._parse._transform import transform_schema

schema = {
    "$ref": "#/$defs/Tier",
    "$defs": {"Tier": {"type": "string", "enum": ["free", "pro", "enterprise"], "title": "Tier"}},
}
transform_schema(schema)
# before: {"$ref": "#/$defs/Tier"}            <- $defs dropped, dangling ref
# after:  {"$defs": {"Tier": {...}}, "$ref": "#/$defs/Tier"}

Fix

Process $defs before the $ref early return. Bare-$ref schemas (no siblings) are unchanged — $defs is only emitted when present in the input.

Not addressed here: other root-$ref siblings (e.g. title) are still dropped by the early return. $defs is the one that makes the schema invalid; the rest are cosmetic.

Tests

  • Added test_ref_schema_with_defs covering the RootModel shape (root $ref + sibling $defs); the existing test_ref_schema covers the bare-$ref case.
  • tests/lib/_parse/test_transform.py: 14/14 pass.

@benlehrburger-ant benlehrburger-ant requested a review from a team as a code owner June 4, 2026 04:04
@benlehrburger-ant benlehrburger-ant force-pushed the benlehrburger/transform-schema-root-ref-defs branch from 26c79b0 to 05d4be3 Compare June 4, 2026 04:06
transform_schema() early-returns when the schema root contains $ref,
which discards a sibling $defs and produces a schema with a dangling
reference. Pydantic v2 emits exactly this shape for RootModel types
({"$ref": "#/$defs/X", "$defs": {...}}), so any RootModel output
type hits this.

Process $defs before the $ref early-return so referenced definitions
are preserved. Bare $ref schemas (no siblings) are unchanged.
@benlehrburger-ant benlehrburger-ant force-pushed the benlehrburger/transform-schema-root-ref-defs branch from 05d4be3 to b775b2a Compare June 4, 2026 04:09
@benlehrburger-ant benlehrburger-ant changed the title fix: preserve $defs when schema root is a $ref in transform_schema fix(transform schema): preserve $defs when schema root is a $ref Jun 4, 2026
@benlehrburger-ant benlehrburger-ant merged commit fc58e06 into main Jun 4, 2026
12 checks passed
@stainless-app stainless-app Bot mentioned this pull request Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants