Skip to content

Commit 802ac99

Browse files
committed
docs(core-internal): note zod <4.3.0 override skip on reused cloned schemas
On zod 4.0-4.2.x, toJSONSchema guards the override hook with 'if (!seen.isParent)' (v4/core/to-json-schema.js), and a schema instance is marked isParent whenever a clone of it (.describe()/.meta()) appears in the same conversion — so a schema reused both bare and via a clone skips sanitization on the bare node: untruthful required/ additionalProperties survive and z.date() emits {}. zod 4.3.0 removed the guard, so the lockfile resolution (4.3.6) and the test suite cannot observe it. Verified against the published 4.2.1 and 4.3.0 tarballs. Document it as a known residual gap and scope the changeset claim; bumping the declared zod floor (^4.2.0 -> ^4.3.0) would close it for the primary path but is a maintainer decision. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 665a056 commit 802ac99

2 files changed

Lines changed: 16 additions & 7 deletions

File tree

.changeset/zod-tojsonschema-wire-truthful.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ and `additionalProperties: false` is dropped for plain `z.object()` (kept for
1616
`z.strictObject()`), so validating clients no longer reject legitimate tool results for
1717
these schema shapes. (Output schemas containing `.transform()`/`.pipe()`/`z.coerce` still
1818
advertise the post-transform shape while the server ships the raw pre-transform value — a
19-
pre-existing gap this change does not address.) Elicitation is unaffected:
19+
pre-existing gap this change does not address. And on zod 4.0–4.2.x, `toJSONSchema` skips
20+
the sanitization hook on a schema reused both bare and via a `.describe()`/`.meta()` clone
21+
in the same conversion; full per-node sanitization requires zod >=4.3.0.) Elicitation is
22+
unaffected:
2023
`inputRequired.elicit()` keeps throwing on schemas its restricted form grammar cannot
2124
round-trip, including `z.date()`.

packages/core-internal/src/util/standardSchema.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,18 @@ export const JSON_SCHEMA_CONVERSION_TARGET = 'draft-2020-12';
191191
* absent from the shipped payload (`.default()`, undefined-accepting types) from
192192
* `required`: zod fills defaults during validation, but ships the raw object.
193193
*
194-
* Known residual gap: output schemas containing `.transform()`/`.pipe()`/`z.coerce`
195-
* still advertise the post-transform shape (`io: 'output'`) even though the server
196-
* validates and ships the raw pre-transform value — rewriting pipe nodes to their
197-
* input side per-node would break `$ref`s to registered schemas, and converting
198-
* output advertisements with input semantics wholesale is a design decision that
199-
* interacts with SEP-2106 non-object output roots (see #2464 discussion).
194+
* Known residual gaps:
195+
* - Output schemas containing `.transform()`/`.pipe()`/`z.coerce` still advertise the
196+
* post-transform shape (`io: 'output'`) even though the server validates and ships
197+
* the raw pre-transform value — rewriting pipe nodes to their input side per-node
198+
* would break `$ref`s to registered schemas, and converting output advertisements
199+
* with input semantics wholesale is a design decision that interacts with SEP-2106
200+
* non-object output roots (see #2464 discussion).
201+
* - On zod 4.0–4.2.x, `toJSONSchema` skips the `override` hook on any node whose
202+
* clone (`.describe()`/`.meta()`) appears in the same conversion (the
203+
* `if (!seen.isParent)` guard in `v4/core/to-json-schema.js`, removed in zod
204+
* 4.3.0), so a schema reused both bare and via a clone leaves the bare node
205+
* unsanitized. Full per-node sanitization requires zod >=4.3.0.
200206
*/
201207
function zodConversionOptions(io: 'input' | 'output'): Pick<z.core.ToJSONSchemaParams, 'unrepresentable' | 'override'> {
202208
return {

0 commit comments

Comments
 (0)