zod correction pack · for projects on zod@^4
What we actually measured
4 Claude models were asked for idiomatic zod code with no tools, purely from training knowledge. 38 reproduced failures across 18 runs, each verified against the release that broke the belief.
| Model | Stated cutoff | zod version attribution stops | Lag inside the window |
|---|---|---|---|
| Claude Opus 5 | 2026-05 | 4.1.0 · 2025-08-23 | ~9 months |
| Claude Sonnet 5 | 2026-01 | 4.0.0 · 2025-07-10 | ~6 months |
| Claude Fable 5 | 2026-01 | 4.1.0 · 2025-08-23 | ~5 months |
A recent cutoff is not a defence. Every model here loses track of this library's release history well before the date it states as its own cutoff, and the stopping points cluster far tighter than the cutoffs do.
Read that column precisely. It is the newest zod release whose contents the model can correctly attribute to that release — not the newest zod feature it knows. Past that point a model will often write working code with a newer API while naming the wrong release for it, and that guess runs early — it names a release older than the one that shipped the feature. So the question this pack answers is not "does the model know this API" but "can it be trusted about which version the API arrived in" — which is the question that matters when you are pinned to a version.
Latest zod is 4.5.4 (verified 2026-08-31). Separately from the corrections below, 4 of 18 runs recorded a version fact — the model named a current zod version from memory and was behind. If a model states a version without checking, assume it is behind and check the registry.
How to read an entry
Every entry ends with a Reproduced against line. Where it names models, we have the generated code that got it wrong, dated, with the model's own words in the run write-up. Where it says no model yet, the correction is verified from the release notes but nothing has been probed for it — it is a fix, not a measurement, and the pack says so rather than blurring the two.
The section an entry sits in is the worst case if you act on the stale belief. The severity in brackets after a model's name is what that particular model's output actually did, which can be milder — a model can hold the wrong belief and still, on the day, write code that runs.
The corrections
Breaks the build, or throws at runtime
Act on the stale belief here and the code does not run. Fix these first.
.pick() / .omit() on a schema with refinements
Now throws in zod 4.3.0 (2025-12-31)
.pick() and .omit() throw when called on an object schema that carries a refinement. They no longer silently drop the refinement. Rebuild from the shape instead: z.object(schema.shape).pick({ ... }).
The stale belief: That .pick()/.omit() on a refined schema succeeds and quietly discards the refinement — true up to 4.2.
// Stale
const Signup = z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine((d) => d.password === d.confirmPassword);
Signup.pick({ password: true }); // 4.3+: throws
// Current
const shape = { password: z.string(), confirmPassword: z.string() };
const Signup = z.object(shape).refine((d) => d.password === d.confirmPassword);
z.object(shape).pick({ password: true }); // derive from the unrefined base
The durable rule for this whole cluster: keep an unrefined
z.object({...})base, derive every variant from that, and apply.refine()last.
Reproduced against: Claude Fable 5 (S1), Claude Opus 5 (S1), Claude Sonnet 5 (S1) — zod/v2, 2026-08-29; zod/v3-a, 2026-09-02; zod/v4-a, 2026-09-02.
Source: Zod 4.3.0 release notes · 2025-12-31
.extend() overwriting a property on a refined schema
Now throws in zod 4.3.0 (2025-12-31)
.extend() throws when it overwrites an existing property on a schema that has refinements. Use .safeExtend(), which preserves the refinement and statically prevents you from changing a property's type signature.
The stale belief: That .extend() may freely overwrite properties on any object schema.
// Stale
const A = z.object({ a: z.string() }).refine(/* ... */);
A.extend({ a: z.number() }); // 4.3+: throws
// Current
A.safeExtend({ a: z.string().min(5).max(10) }); // allowed, refinement preserved
.safeExtend()has existed since 4.1.0, so it is safe to use on any 4.1+ project.
Reproduced against: Claude Opus 5 (S1), Claude Sonnet 5 (S1) — zod/v2, 2026-08-29.
Source: Zod 4.3.0 release notes · 2025-12-31 · Zod 4.1.0 release notes · 2025-08-23
z.record()
Removed in zod 4.0.0 (2025-07-10)
Write z.record(keySchema, valueSchema). It is the only form that works across the whole v4 line. Zod 4.0 removed the v3 single-argument form; 4.4.0 restored it at runtime only — the type declaration was never given a one-argument overload, so z.record(z.number()) still fails to compile on every v4 release including 4.5.4 (TS2554: Expected 2-3 arguments, but got 1). In JavaScript, or with the type error suppressed, the single-argument call behaves differently either side of 4.4.0: on 4.0.0–4.3.6 it constructs and then rejects every key with invalid_key, and from 4.4.0 it validates values as the v3 form did.
The stale belief: That z.record(z.number()) — the Zod 3 single-argument form — is current. Or, after reading the 4.4.0 release note, that it is current again.
// Stale
z.record(z.number()); // TS2554 on every v4 release; also rejects every key before 4.4.0
// Current
z.record(z.string(), z.number());
Re-verified 2026-09-02 against the shipped packages rather than the release note, and the release note is the trap.
tsc --strictreports TS2554 on 4.3.6, 4.4.0, 4.4.3 and 4.5.4 alike, andv4/classic/schemas.d.tsdeclares the same single two-argumentrecordoverload byte-identically from 4.0.0 through 4.5.4. The runtime half is true and was executed: at 4.0.0 and 4.3.6z.record(z.number()).safeParse({a:1})failsinvalid_key; at 4.4.0 and 4.5.4 it passes, and{a:"nope"}failsinvalid_type— so the single argument really is the value schema again. This entry is the reason the pack is generated. The hand-written version said the form was removed in v4 and will not compile; the 2026-08-31 replacement then read the 4.4.0 note as restoring the form outright, which is wrong for every TypeScript reader. Both errors came from a release note, and neither survived running the code.
Reproduced against: Claude Haiku 4.5 (S1) — zod/v1, 2026-08-28.
Source: Zod 4 changelog — Record Schema Changes · Zod 4.4.0 release notes — accurate for the runtime, silent about the unchanged type signature · 2026-04-29 · zod 4.5.4 shipped package — v4/classic/schemas.d.ts declares one two-argument record overload; tsc --strict reports TS2554 for the single-argument call
.merge() on a schema with refinements
Now throws in zod 4.4.0 (2026-04-29)
.merge() throws when the receiver has refinements. Prefer .extend() / .safeExtend() for object composition; .merge() is discouraged for new code regardless.
The stale belief: That .merge() is the normal way to combine two object schemas.
// Stale
const a = z.object({ a: z.string() }).refine((val) => val.a.length > 0);
a.merge(z.object({ b: z.string() })); // 4.4+: throws
// Current
z.object({ a: z.string(), b: z.string() }).refine((val) => val.a.length > 0);
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.4.0 release notes · 2026-04-29
.pick() / .omit() with a key that does not exist
Now throws in zod 4.3.0 (2025-12-31)
Object masking methods now validate that the keys you pass actually exist on the schema, and throw on an unrecognized key.
The stale belief: That an unknown key in a .pick()/.omit() mask is ignored.
// Stale
z.object({ a: z.string() }).pick({ nonexistent: true }); // 4.3+: throws
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.3.0 release notes · 2025-12-31
Runs, but is silently wrong
Nothing errors. The behaviour is simply not what a model trained earlier will tell you.
z.fromJSONSchema()
Added in zod 4.2.0 (2025-12-15)
Zod converts JSON Schema to Zod at runtime. Do not add Ajv or json-schema-to-zod for this. Supports draft-2020-12, draft-7, draft-4 and OpenAPI 3.0. z.toJSONSchema() is the other direction.
The stale belief: That Zod's JSON Schema support is one-way (Zod to JSON Schema only).
const schema = z.fromJSONSchema({
type: "object",
properties: { name: { type: "string", minLength: 1 } },
required: ["name"],
});
Upstream calls the API experimental and makes no round-trip soundness guarantee.
Reproduced against: Claude Fable 5 (S2), Claude Opus 5 (S2), Claude Sonnet 5 (S2) — zod/v2, 2026-08-29; zod/v4-a, 2026-09-02.
Source: Zod 4.2.0 release notes · 2025-12-15 · Zod 4.3.0 release notes · 2025-12-31 · zod@4.5.4 published type declarations, v4/classic/from-json-schema.d.ts
z.xor()
Added in zod 4.2.0 (2025-12-15)
Exclusive union: passes only when exactly one option matches, failing on zero matches and on more than one. Do not hand-roll it with double safeParse plus superRefine.
The stale belief: That Zod has only z.union() (any match) and cannot express exclusivity.
const schema = z.xor([z.string(), z.number()]);
schema.parse("hello"); // ok
schema.parse(true); // fails: zero matches
Converts to
oneOfrather thananyOfin JSON Schema output.
Reproduced against: Claude Fable 5 (S2), Claude Opus 5 (S2), Claude Sonnet 5 (S2) — zod/v2, 2026-08-29; zod/v4-a, 2026-09-02.
Source: Zod 4.2.0 release notes · 2025-12-15 · Zod 4.3.0 release notes · 2025-12-31 · zod@4.5.4 published type declarations, v4/classic/schemas.d.ts
object properties typed z.undefined()
Behaviour changed in zod 4.4.0 (2026-04-29)
A property whose schema accepts undefined and is not .optional() is required: the key must be present, the value may be undefined. Zod can therefore distinguish a missing key from an explicit undefined — no z.preprocess or in check needed. Use .optional() only when the key itself may be absent.
The stale belief: That Zod cannot tell a missing key from a key explicitly set to undefined.
const schema = z.object({ value: z.undefined() });
schema.safeParse({}).success; // false
schema.safeParse({ value: undefined }).success; // true
Also changes
.catch(),.partial(),.default()and.prefault()combinations that relied on missing keys being treated as optional.
Reproduced against: Claude Fable 5 (S2, not chargeable), Claude Opus 5 (S4), Claude Sonnet 5 (S2, not chargeable) — zod/v2, 2026-08-29.
Source: Zod 4.4.0 release notes · 2026-04-29
z.httpUrl()
Stricter in zod 4.4.0 (2026-04-29)
z.httpUrl() is the validator for http/https URLs — it checks the protocol and that the hostname is a domain — and since 4.4.0 it rejects a missing slash after the protocol instead of accepting the value the URL constructor silently repairs. Do not hand-roll a normalization step, and do not reach for z.url({ protocol: /^https?$/ }).
The stale belief: That URL validation inherits the WHATWG URL constructor's leniency, so "https:/example.com" has to be normalized by hand.
// Stale
z.url({ protocol: /^https?$/ }); // plus a manual normalization pass
// Current
z.httpUrl().safeParse("https://example.com").success; // true
z.httpUrl().safeParse("https:/example.com").success; // false, since 4.4.0
Matters most where the URL is a webhook target or is later compared as a string.
Reproduced against: Claude Opus 5 (S2), Claude Sonnet 5 (S2) — zod/v2, 2026-08-29; zod/v3-a, 2026-09-02.
Source: Zod 4.4.0 release notes · 2026-04-29
.exactOptional()
Added in zod 4.3.0 (2025-12-31)
Makes a property key-optional (may be omitted) while still rejecting an explicit undefined value — the missing half of exactOptionalPropertyTypes.
const schema = z.object({
a: z.string().optional(), // accepts undefined
b: z.string().exactOptional(), // does not accept undefined
});
Reproduced against: Claude Fable 5 (S2), Claude Opus 5 (S2), Claude Sonnet 5 (S2, not chargeable) — zod/v3-a, 2026-09-02; zod/v4-a, 2026-09-02.
Source: Zod 4.3.0 release notes · 2025-12-31 · zod@4.5.4 published type declarations, v4/classic/schemas.d.ts
intersections involving z.strictObject()
Behaviour changed in zod 4.3.0 (2025-12-31)
An intersection now rejects only the keys unrecognized by both sides. Previously an unrecognized key from either side errored.
const C = z.intersection(z.strictObject({ a: z.string() }), z.object({ b: z.string() }));
C.parse({ a: "foo", b: "bar" }); // ok in 4.3+
Reproduced against: Claude Fable 5 (S2), Claude Sonnet 5 (S2, not chargeable) — zod/v4-a, 2026-09-02.
Source: Zod 4.3.0 release notes · 2025-12-31
z.tuple() defaults
Behaviour changed in zod 4.4.0 (2026-04-29)
Defaults in tuple positions materialize in parsed output rather than erroring on an under-filled input.
The stale belief: That an under-filled tuple is a hard length error regardless of defaults.
z.tuple([z.string(), z.string().default("fallback")]).parse(["a"]);
// ["a", "fallback"]
Trailing optional elements that are absent stay absent; an explicit
undefinedsupplied by the caller is preserved. Becausez.function()arguments are tuple-shaped, function input errors may also look different.
Reproduced against: Claude Sonnet 5 (S2, not chargeable) — zod/v2, 2026-08-29.
Source: Zod 4.4.0 release notes · 2026-04-29
z.compile()
Added in zod 4.5.0 (2026-08-28) · published after every cutoff in this dataset — listed for completeness, no model can be faulted for it yet
z.compile(schema) pre-compiles a schema and parses roughly 3–9x faster on objects, arrays and unions. A compiled schema is used exactly like an uncompiled one.
const CompiledPlayer = z.compile(Player);
CompiledPlayer.parse({ /* ... */ });
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.5.0 release notes · 2026-08-28
record key transforms
Behaviour changed in zod 4.4.0 (2026-04-29)
Record schemas now run transforms on record keys, so a key transform changes the parsed output shape.
The stale belief: That a transform on the key schema of a record is inert.
z.record(z.string().transform((k) => k.toUpperCase()), z.number()).parse({ foo: 1 });
// { FOO: 1 }
Key refinement failures now surface as structured
invalid_keyissues.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.4.0 release notes · 2026-04-29
z.base64()
Stricter in zod 4.4.0 (2026-04-29)
z.base64() rejects whitespace instead of allowing atob()-style whitespace stripping.
The stale belief: That line-wrapped base64 (PEM, MIME) validates.
z.base64().safeParse("Zm9v").success; // true
z.base64().safeParse("Zm 9v").success; // false, since 4.4.0
Strip whitespace before the check if you need to accept wrapped input.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.4.0 release notes · 2026-04-29
z.slugify()
Added in zod 4.3.0 (2025-12-31)
String-to-slug transform. Do not hand-roll a regex chain.
z.string().slugify().parse("Hello World"); // "hello-world"
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.3.0 release notes · 2025-12-31 · zod@4.5.4 published type declarations, v4/classic/schemas.d.ts
z.looseRecord()
Added in zod 4.2.0 (2025-12-15)
Validates only the keys matching the key schema and passes non-matching keys through unchanged — the representation of JSON Schema patternProperties.
z.looseRecord(z.string().regex(/^S_/), z.string()).parse({ S_name: "John", other: 123 });
// { S_name: "John", other: 123 } — only S_name is validated
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.2.0 release notes · 2025-12-15 · Zod 4.3.0 release notes · 2025-12-31
z.codec() / z.encode() / z.decode()
Added in zod 4.1.0 (2025-08-23)
A codec is a bidirectional transformation and replaces a pair of one-way .transform() schemas kept in sync by hand. z.invertCodec() (4.4.0) flips one.
The stale belief: That a round-trip needs two separate .transform() schemas.
The code below needs zod 4.4.0 or later. Between 4.1.0 and 4.4.0 this correction does not apply — see the note.
const stringToDate = z.codec(z.iso.datetime(), z.date(), {
decode: (isoString) => new Date(isoString),
encode: (date) => date.toISOString(),
});
const dateToString = z.invertCodec(stringToDate); // 4.4.0
AUDIT 2026-09-02 (JOURNAL/038). The floor is on the last line only:
z.codec()is the 4.1.0 arrival this fact is filed under and compiles from 4.1.0, butz.invertCodec()arrived at 4.4.0, so the block as printed is TS2339 on 4.1.0, 4.2.0 and 4.3.0 — bisected against the installed packages. The fact stated 4.4.0 in prose and in a trailing comment and still carried noreplacement_available_from, which is why the sweep of all 168 facts in JOURNAL/037 read past it and the mechanical audit did not.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.1.0 release notes · 2025-08-23 · Zod 4.4.0 release notes · 2026-04-29
defaults inside optionals
Behaviour changed in zod 4.0.0 (2025-07-10)
A default applies inside an optional: z.object({ a: z.string().default("tuna").optional() }) parses {} to { a: "tuna" }.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4 changelog — Defaults
z.coerce.*
Behaviour changed in zod 4.0.0 (2025-07-10)
z.coerce.* accepts unknown input in v4, and an object with coerced fields errors on a missing key instead of silently defaulting. Declare the fallback with .default().
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4 changelog — Coercion
Deprecated, or a better API now exists
Works today. It is the older idiom, and some of it is scheduled for removal.
custom error messages
Deprecated in zod 4.0.0 (2025-07-10)
Custom errors use the error parameter: z.email({ error: "..." }). Not message, invalid_type_error or required_error.
The stale belief: The most common half-migration is a v4 format function carrying the v3 message param.
// Stale
z.email({ message: "Invalid email" });
// Current
z.email({ error: "Invalid email" });
Reproduced against: Claude Haiku 4.5 (S3), Claude Sonnet 5 (S3) — zod/v1, 2026-08-28; zod/v1, 2026-08-29.
Source: Zod 4 changelog — Error Customization
string format validators
Deprecated in zod 4.0.0 (2025-07-10)
Format validators are top-level functions in v4: z.email(), z.uuid(), z.url(), z.ipv4(). The method-style z.string().email() form is the v3 idiom.
// Stale
z.string().email();
// Current
z.email();
Reproduced against: Claude Haiku 4.5 (S3) — zod/v1, 2026-08-28.
Source: Zod 4 changelog — String Format Validators
ZodError formatting
Deprecated in zod 4.0.0 (2025-07-10)
Shape errors for a form with z.treeifyError(result.error) or z.flattenError(result.error), not the .flatten() / .format() methods on the error object.
// Stale
result.error.flatten();
// Current
z.treeifyError(result.error);
Reproduced against: Claude Haiku 4.5 (S3) — zod/v1, 2026-08-28.
Source: Zod 4 changelog — Error Formatting
z.creditCard() / z.properties() / .deepPartial() / .exactPartial()
Added in zod 4.5.0 (2026-08-28) · published after every cutoff in this dataset — listed for completeness, no model can be faulted for it yet
4.5.0 added z.creditCard() (12–19 digits plus a Luhn checksum) and z.properties() as the multi-property counterpart to z.property(), and returned .deepPartial() / .exactPartial().
A model asserting
.deepPartial()was removed is correct as of its own cutoff, not stale — it came back in 4.5.0.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.5.0 release notes · 2026-08-28
z.validate()
Added in zod 4.5.0 (2026-08-28) · published after every cutoff in this dataset — listed for completeness, no model can be faulted for it yet
z.validate(schema, input) returns a boolean without a full parse — a fast path when you only need validity.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.5.0 release notes · 2026-08-28
.superRefine({ when })
Added in zod 4.4.0 (2026-04-29)
.superRefine() takes a when option for conditional refinement, so a guard clause inside the callback is no longer the only way to express it.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.4.0 release notes · 2026-04-29
empty z.union([]) / z.xor([])
Behaviour changed in zod 4.4.0 (2026-04-29)
Empty unions and discriminated unions construct without crashing and fail at parse time instead of construction time.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.4.0 release notes · 2026-04-29
z.cuid()
Stricter in zod 4.4.0 (2026-04-29)
CUID validation was tightened and CUID v1 is deprecated.
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.4.0 release notes · 2026-04-29
.with()
Added in zod 4.3.0 (2025-12-31)
.with() is the readable alias for .check(), added because not everything composable is a 'check'.
z.string().with(z.minLength(5), z.toLowerCase());
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4.3.0 release notes · 2025-12-31 · zod@4.5.4 published type declarations, v4/classic/schemas.d.ts
z.function()
Behaviour changed in zod 4.0.0 (2025-07-10)
z.function() is a factory, not a schema: build it with { input, output } and call .implement() / .implementAsync().
const fn = z.function({ input: [z.string()], output: z.number() })
.implement((s) => s.length);
Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.
Source: Zod 4 changelog — z.function()
Wrong facts about the library
Not code — versions, minimums and metadata that models state confidently and get wrong.
installing and importing zod
Behaviour changed in zod 4.0.0 (2025-07-10)
npm install zod has installed v4 since 4.0.0 and the import path is plain "zod". Generated code does not need a v3/v4 fallback branch, and zod/v4 is not the subpath to reach for on a new project.
The stale belief: That v4 is opt-in behind a subpath, so generated code should hedge with a v3 fallback block.
import * as z from "zod";
Reproduced against: Claude Sonnet 5 (S4) — zod/v1, 2026-08-29.
Source: Zod 4 changelog
Not corrections — recorded for honesty
These are real reproduced failures that we do not charge to the model, because the release that made the belief wrong was published after that model's stated cutoff. They are scheduled retests, not passes.
- Claude Fable 5 · claims Zod cannot enforce key presence with an undefined value — 4.4.0 postdates this subject's stated 2026-01 cutoff. Recorded as context and as a retest target for the next Fable release.
- Claude Sonnet 5 · claims Zod cannot distinguish a missing key from an explicit undefined — 4.4.0 postdates this subject's stated 2026-01 cutoff, so this is outside the probe fairness window and is recorded as context, not charged. It IS chargeable against Opus 5 (F6 of that run), whose cutoff is 2026-05.
- Claude Sonnet 5 · wrong runtime prediction for tuple defaults — Same as F5 — 4.4.0 postdates the stated 2026-01 cutoff. Recorded as context and as a retest target.
- Claude Sonnet 5 · Denies that the library has any way to make an object key omittable while rejecting an explicit undefined value, and argues the gap is structural — NOT chargeable against this draw. 4.3.0 published 2025-12-31; this draw stated its own cutoff as "roughly early-to-mid 2025" and explicitly repudiated the January 2026 its environment reported. Under the probe fairness rule a finding is chargeable only where the release precedes the subject's STATED cutoff, and this one does not. It becomes chargeable against any draw of this subject that states a cutoff after 2025-12-31 - which this battery's own blind twin did, in the arm that charges nothing.
- Claude Sonnet 5 · Denies that the library can consume a JSON Schema document at runtime and prescribes Ajv as a new dependency — NOT chargeable against this draw, for the same reason as F1: 4.2.0 published 2025-12-15, after the "early-to-mid 2025" this draw states for itself.
- Claude Sonnet 5 · Denies that the library has an exclusive union primitive — NOT chargeable against this draw's stated cutoff of "roughly early-to-mid 2025".
- Claude Sonnet 5 · States that an intersection of two strict object schemas cannot parse an object carrying keys from both sides — NOT chargeable against this draw's stated cutoff.
Claims seen in a run but not yet verified against a primary source. Never treated as findings:
- Did a trailing-
?key-optional object constructor (z.interface()) ever ship in a published Zod 4 beta, as both Fable 5 draws assert - "the Zod 4 betas had this, via z.interface() and its 'nickname?' key syntax, and it was removed before 4.0 stable"? (open since 2026-09-02) - Did a trailing-
?key-optional object constructor (z.interface()) ever ship in a published Zod 4 beta? (open since 2026-09-02) - Zod 4 coercion accepts unknown input and changed missing-key behaviour for objects with coerced fields. Does this subject know either change? (open since 2026-08-28)
- This draw named
.safeExtend()as a 4.1 addition that "exists precisely because plain.extend()handles existing object-level checks unsoundly" while simultaneously asserting that Zod 4 lifted the restriction on.pick()/.extend()over refined schemas. Those two statements are in tension and the battery has no way to score a subject that holds both. The Index's finding F2 charges the second; the first is closer to right than anythingzod/v2recorded. (open since 2026-09-01) - This draw raised
z.interface()and said it could not recall whether it survived into stable Zod 4 - "I have conflicting recollections" - and declined to build task 7 on it. The Index has never verified what became ofz.interface(). It is cheap archaeology from the 4.0.0 release notes and it would let a future battery score the question rather than watch a subject hedge past it. (open since 2026-09-01) - Did an object-schema constructor using trailing-
?key syntax ever exist in a published zod build? This draw placed it in "the Zod 4 beta, published as zod@3.25.0 under the zod/v4 subpath, ~late May 2025", and could not say whether it survived to stable. (open since 2026-09-02) - Does z.number().int().min(18, "Must be at least 18") — the bare-string shorthand second argument — remain supported in v4 alongside { error }? (open since 2026-08-29)
Findings, code and citations: data/zod/ — one JSON file and one write-up per model, each finding carrying the release that broke the belief, its publication date and a verbatim quote from the primary source. Corrections: data/zod/facts.json. This file is generated by tools/build-corrections.mjs; if the prose and the data ever disagree, that is a bug in the generator, not a stale pack.