Skip to content

deps: eradicate Zod 3 and align Zod on npm — three Zod instances coexist and npm peers requiring ^4.0.0 resolve to 3.25.76 #1295

Description

@rickylabs

Type soundness is a primary selling point (#1278). Right now the dependency graph quietly holds three different Zod module instances, and packages that require Zod 4 are being handed Zod 3.

Measured on origin/main

deno info npm:@modelcontextprotocol/sdk
  peer zod@^4.0.0: resolved to 3.25.76      ← violated constraint, not a preference
  npm:/zod@3.25.76
deno info npm:@anthropic-ai/sdk   → peer zod@^4.0.0: resolved to 3.25.76
deno info npm:openai              → peer zod@^4.0.0: resolved to 3.25.76  (×2 paths)

The three instances in one lock:

Instance Who binds to it
jsr:@zod/zod@4.4.3 18 workspace deno.json files — all of our own code
npm:zod@4.4.3 better-call@1.3.6 (better-auth path)
npm:zod@3.25.76 @anthropic-ai/sdk, @modelcontextprotocol/sdk, openai, @tanstack/ai-mcp, zod-to-json-schema

Most of the cluster does not pin v3 — but two paths do (corrected 2026-08-05)

Every package in that v3 cluster already accepts Zod 4:

Package peerDependencies.zod
@anthropic-ai/sdk ^3.25.0 || ^4.0.0
@modelcontextprotocol/sdk ^3.25 || ^4.0
openai ^3.25 || ^4.0
zod-to-json-schema (3.25.2, latest) ^3.25.28 || ^4

So there is no upstream blocker to remove — this is ours to fix. The npm subgraph has no npm-side Zod 4 to dedupe onto because our own code supplies Zod from JSR, so Deno resolves those peers independently and lands on 3.25.76.

Why it is a correctness problem, not tidiness

A schema built with jsr:@zod/zod is a different module instance from npm:zod@4.4.3 and from npm:zod@3.25.76. Across that boundary instanceof fails, brand/nominal checks fail, and structurally-identical types stop being assignable — the class of failure that produced #1250 (Zod-3 plugin against Zod-4 scaffolds, every numeric query param 400ing) and #1249. It also means an AI-plugin path can hand a v4 schema to an SDK holding v3.

Also: the catalog is npm-only (AGENTS.md), so while Zod comes from JSR it cannot live in the catalog — there is no single source of version truth for the repo's most cross-cutting dependency.

Acceptance (rescoped 2026-08-05 — see the correction comment)

The original "exactly one Zod instance" bar is not reachable in 0.0.5: @ag-ui/core@0.0.52
hard-depends on zod: ^3.22.4 with no || ^4, and jsr:@olli/kvdex@3.6.7 (latest) also
materialises Zod 3. Collapsing to one instance needs either a TanStack AI 0.39→0.43 cluster
upgrade — which currently drags in a canary AG-UI build — or replacing/forking/removing
kvdex. Neither belongs in a release we are about to cut.

Scoped to what is real and verifiable now:

  • The AI/MCP peer cluster (@anthropic-ai/sdk, @modelcontextprotocol/sdk, openai,
    zod-to-json-schema) resolves onto npm Zod 4 — no peer zod@^4.0.0: resolved to 3.x
    line survives for any of them in deno info
  • Workspace Zod expressed once in the root catalog rather than repeated across 18
    deno.json files
  • A guard test fails CI on a split Zod graph, so the boundary can never regress silently
  • @orpc/zod usage is on the v4 surface throughout (consistent with the service: ZodSmartCoercionPlugin imported from @orpc/zod (Zod 3) while scaffolds use Zod 4 — coercion silently no-ops, every numeric query param 400s #1250 fix)
  • The remaining two-instance boundary is documented with its exact blocking dependencies
    (@ag-ui/core hard ^3.22.4, jsr:@olli/kvdex@3.6.7) rather than left implicit
  • publish:dry-run and deno doc --lint stay clean across the 18-package change

Collapsing to a single instance moves to 0.0.6, gated on AG-UI accepting Zod 4 or a kvdex
decision. Filed separately so it is tracked rather than forgotten.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions