What Claude Fable 5.1 gets wrong about zod — battery v6-a, tested 2026-09-06

Run zod--claude-fable-5-1--v6-a--2026-09-06

Summary

Claude Fable 5.1 sent the byte-identical prompt that zod/v4 sent Claude Fable 5, Claude Sonnet 5 and Claude Haiku 4.5. It answered the three capability probes correctly where Claude Fable 5 denied all three, which falsifies the battery's pre-registered prediction and moves its zod boundary from 4.1.0 to 4.2.0. It failed the two behavioural probes, both charged: the refined-schema derivation asserted to load and drop silently (4.3.0, S1) and the strict-object intersection asserted structurally unsatisfiable (4.3.2, S2). Verifying the second against the artifact rather than the release note corrected two of the Index's own facts.

SubjectClaude Fable 5.1 claude-fable-5-1, Anthropic
Invoked asAgent tool, model alias "fable", general-purpose subagent, instructed to use no tools; CHARGING TEST ARM of battery zod/v6. Prompt sent verbatim from prompts/sent/zod-v4.txt, byte-identical to the file sent to zod/v4-a, v4-b and v4-c. Identity probed through the same alias in the same session, tool-free: "Fable 5.1", `claude-fable-5-1`, cutoff June 2026 — and the probe volunteered that all three came from its system prompt rather than from self-knowledge, adding that its own knowledge tells it only that it is Claude by Anthropic.
Cutoff the model states2026-06
Newest zod release it could place4.2.0 · 2025-12-15 (~6 month lag)
Oldest zod release it could not place4.3.0 · 2025-12-31 (so this run brackets the subject’s boundary to 2025-12-15 – 2025-12-31)
In its own words"The latest version I know of is the 4.2 line, first released around December 2025 (possibly slipping into early January 2026), followed by 4.2.x patches into early 2026. That is also the most recent release whose contents I can actually describe ... I have a faint sense that a 4.3 may exist by mid-2026 but no content attached to it, so I would not claim it."
Library at test timezod 4.5.4 (npm), verified 2026-09-06
Batteryzod/v6-a · 6 tasks, 4 direct questions · probe window 4.2.0 to 4.3.0
Tool uses during test0 (a run with any tool use is void — we measure training knowledge, not retrieval)
Tested2026-09-06
Findings2, of which 2 chargeable

Findings

F1 · States that deriving a sub-schema from a refined object schema loads without error and silently drops the refinement - the exact behaviour 4.3.0 replaced with a throw

S1breaks-build · .pick() / .omit() on a schema with refinements · now-throws · changed in zod 4.3.0 (2025-12-31) · chargeable

4.3.0 (2025-12-31) precedes this subject's stated cutoff of 2026-06 by five months, and precedes even the 'turn of 2025/2026' density qualification the subject volunteered at (b) by one day. Bisected: 4.2.1 does not throw, 4.3.0 does.

What the model believes

"The module loads without error. In Zod 4, .refine() on a ZodObject returns the same ZodObject with the refinement appended to its internal checks list, so .pick exists both at the type level and at runtime." And: "PasswordOnly is z.ZodObject<{ password: z.ZodString }> and the refinement is gone: .pick(), .omit(), .extend() and .partial() all rebuild the object with an empty checks list ... So PasswordOnly.parse({ password: \"x\" }) succeeds."

What it wrote
const PasswordOnly = Signup.pick({ password: true });   // asserted to load and yield ZodObject<{password}>
What works on zod 4.5.4
const shape = { password: z.string(), confirmPassword: z.string() };
const Signup = z.object(shape).refine((d) => d.password === d.confirmPassword);
const PasswordOnly = z.object(shape).pick({ password: true });   // derive from the unrefined base
Impact

A precise, confident description of pre-4.3 behaviour stated as current, complete with a correct account of the mechanism (refinements moved inside the schema in v4) and a correct contrast against Zod 3. On any zod at or above 4.3.0 the line throws at module evaluation, so the service fails to start rather than failing a parse. The draw's own recommended rewrite is the correct fix and it reaches it for an unrelated reason - error-path placement on the form field - so a reader who follows the prose rather than the final snippet ships the broken form.

Scope note

Executed across the line this session: at 4.1.0, 4.1.12, 4.2.0 and 4.2.1 the module loads and PasswordOnly.parse({password:"x"}) succeeds with the refinement dropped, exactly as this draw describes; at 4.3.0 and every release through 4.5.4 it throws .pick() cannot be used on object schemas containing refinements. The draw's account of the v3-to-v4 mechanism change is correct and its .safeExtend() aside is correct; what is stale is the consequence it draws for 4.3+.

Verified against
  • Zod 4.3.0 release notes published 2025-12-31

    Using .pick() or .omit() on object schemas with refinements now throws an error. Previously, this would silently drop the refinements, leading to unexpected behavior.

F2 · States that an intersection of two strict object schemas can never be satisfied, and advises against the construction on that basis - true of the library for the first 49 minutes of 4.3.x and false ever since

S2silently-wrong · intersections involving z.strictObject() · behavior-changed · changed in zod 4.3.2 (2025-12-31) · chargeable

4.3.2 (2025-12-31) precedes this subject's stated cutoff of 2026-06 by five months. The version attribution is corrected from 4.3.0 to 4.3.2 by this run's bisect (see LF20); the DATE is unchanged, because 4.3.0, 4.3.1 and 4.3.2 all published on 2025-12-31 within 49 minutes of each other, so no fairness arithmetic anywhere in the Index moves.

What the model believes

"C.parse({ a: \"x\", b: \"y\" }) throws a ZodError with two issues ... There is no input that satisfies this schema except one that is rejected by neither side, which is impossible once both sides have a required key. The fix is to build one object instead of intersecting two." The draw reproduced the exact issue array it expected, both unrecognized_keys at the root.

What it wrote
const C = z.intersection(z.strictObject({ a: z.string() }), z.strictObject({ b: z.string() }));
C.parse({ a: "x", b: "y" });   // asserted to throw two unrecognized_keys issues, always
What works on zod 4.5.4
const C = z.intersection(z.strictObject({ a: z.string() }), z.strictObject({ b: z.string() }));
C.parse({ a: "x", b: "y" });   // 4.3.2+: { a: "x", b: "y" }
Impact

The belief is not that a call fails - it is that a construction is structurally impossible, and it is stated as a design rule ("don't intersect strict objects"). A reader carrying it rewrites working schema composition to avoid an intersection that has worked for eight months, and will not revisit the belief because it is held as a fact about the type system rather than about a version.

Scope note

Bisected this session across eleven published releases. 4.1.0 through 4.3.1 throw two root unrecognized_keys issues, exactly as this draw states. From 4.3.2 through 4.5.4 the same parse returns {"a":"x","b":"y"}. The 4.3.0 release note announced the change 49 minutes before the code that implements it shipped in 4.3.2 - which is why the Index's own LF20 carried 4.3.0 until this run, and why it now carries 4.3.2. The charge is on the general claim of unsatisfiability, which is false of every zod published since.

Verified against

What it got right, and near misses

Recorded so the run cannot be read as a hit list. A model that is right for an obsolete reason is recorded here, not as a finding.

KindAPINote
context P1 AND P2 BOTH FALSIFIED, and this is the result of the battery. The pre-registration predicted that Claude Fable 5.1 would agree with Claude Fable 5 on every probe, because the two subjects' measured zod boundaries were identical at 4.1.0 while their stated cutoffs differ by five months. It did not. Claude Fable 5 denied all three capability probes; Claude Fable 5.1 answered all three CORRECTLY and unprompted, naming z.exactOptional(), z.xor() and z.fromJSONSchema() without ever being given a name, and writing working code for each. Probes 1, 2 and 3 charge nothing against this subject. (A correct answer is not a finding. It is recorded because it falsifies the battery's headline prediction and moves this subject's boundary.)
context THE BOUNDARY MOVED BETWEEN TWO BATTERIES ON THE SAME SUBJECT AND LIBRARY, WHICH JOURNAL/030 SAID DOES NOT HAPPEN. zod/v5, the boundary ladder run earlier today, recorded knowledge_stops_at_version: 4.1.0 for this subject from its own words: "The most recent release whose contents I can genuinely describe is 4.1.0 (codecs), roughly August 2025." This battery, same subject, same day, different prompt, records 4.2.0 - and not as a self-report to be taken on trust, but demonstrated: the draw produced three separate 4.2.0 APIs it was never handed, with correct signatures and correct semantics. HARNESS's A different battery is not a different boundary rests on next.js, where two batteries two days apart put all three subjects on identical boundaries. This is the first counter-case, and it runs in the direction that matters: the ladder UNDERSTATED what the subject holds, by one minor. (It is a property of the instrument, and the correction belongs to the boundary field rather than to a finding. Never re-charge a recency finding across batteries (JOURNAL/030).)
context THE SUBJECT MISSED A 4.2.0 FEATURE INSIDE ITS OWN CLAIMED RANGE. Probe 6 asked for a slug derived inside the schema. The draw hand-rolled an NFKD-normalising regex chain and never mentioned z.string().slugify() - which this session's bisect proves is present and working in 4.2.0, the very release the draw says is "the most recent release whose contents I can actually describe". Its own list at (c) enumerates four 4.2.0 features and slugify is not among them. So the boundary at 4.2.0 is an attribution boundary, not a coverage claim: the subject can describe some of 4.2.0, not all of it. (Probe 6 charges nothing by construction (JOURNAL/030): the capability was asked for and never the name, the draw did not deny the capability exists, and the hand-rolled slugify was executed and produces "hello-world" correctly. A working workaround is not a finding however thoroughly a first-class API supersedes it.)
context .exactOptional() MISPLACED BY ONE MINOR, WITH A CONCRETE FAILURE MODE THE INDEX HAS NO RULE FOR. The draw produced the correct API and correct semantics, then dated it to 4.2.0 at both task 1 ("added in Zod 4.2") and (d)(ii) ("4.2.0 (estimate, ~Dec 2025; confident it is a minor, not a patch)"). It is 4.3.0 - bisected: absent at 4.2.1, present at 4.3.0. The draw then acted on the misdating, writing "I would ship z.exactOptional and require Zod >= 4.2" and offering a pre-4.2 fallback. A reader who pins ^4.2.0 on that advice gets a TypeError, and the fallback is offered for the wrong bracket. (The Index charges denials of capabilities and false statements about behaviour; it records version attribution as a measured quantity (HARNESS: Version attribution has minor granularity) rather than charging it, and no published finding anywhere in the corpus charges a misdating alone. Inventing that rule inside the session that would benefit from it is the wrong order. Queued in BACKLOG as a rule question: an attribution error that generates a version pin has a failure mode a pure attribution error does not.)
context The internal control (d)(i) was placed correctly. The two-argument requirement on z.record() (LF5, 4.0.0, 2025-07-10) was dated "4.0.0 (July 2025); already in the April 2025 beta. Confident." Per JOURNAL/030 that makes this draw's other attribution answers readable. (Correct answer on the internal control.)

Open questions from this run

Sources

Battery specification: prompts/zod.md in the studio repo. Every finding above also carries its own citation.