Skip to content

deps: bump zod v3 → v4 (53 files, perf + bundle-size win, biggest lift) #1394

@namastex888

Description

@namastex888

Why this issue exists

Pinned zod at 3.25.76 (current resolved) in PR fix/macos-tui-hardening. zod is 1 major behind (latest 4.3.6, stable since mid-2025). Tracking the migration as a separate focused PR — biggest blast radius of the four bump candidates.

How we use it

Blast radius: 53 files import zod. API distribution:

Method Calls
z.string 216
z.number 72
z.enum 52
z.array 49
z.boolean 42
z.object 29
z.literal 16
z.record 14
z.union 12
z.null 4
z.unknown 1
z.preprocess 1

Used everywhere there's structured validation: src/types/genie-config.ts (the central config schema), event payload schemas under src/lib/events/schemas/, wish/task validators, MCP tool schemas, executor input/output validation. This is one of the most foundational dependencies in the codebase.

Two import shapes:

import type { z } from 'zod';
import { z } from 'zod';

What's changed (3.25.76 → 4.3.6)

Architecture change

  • v4 splits the package into three:
    • zod — the high-level API (what we use today)
    • @zod/mini — tree-shakable variant (smaller bundle, separate import path)
    • @zod/core — shared parser/logic primitives, version-pinned to zod
  • Stable since 4.0.0; current latest 4.3.6 (Jan 2026).

Notable v4 highlights (from migration guide)

  • Faster parsing — major perf rewrite, especially for z.object and z.string validation hot paths.
  • Better TypeScript inference.parse() and .safeParse() return types are narrower; intersections and unions infer cleaner.
  • Tree-shaking via @zod/mini — every method is a standalone import, so unused validators don't bloat dist/genie.js.
  • Discriminated union improvements — automatic discrimination, no need for .discriminatedUnion().
  • Native JSON schema export removed from core (extracted to a separate package).
  • z.coerce API may have changed signature.
  • z.record(K, V) — v3 allowed z.record(V) shorthand; v4 may require both args.
  • Error formattingz.ZodError shape and .format() output changed.
  • .refine() and .transform() chaining — order semantics tightened.

(Full migration guide: https://zod.dev/v4/changelog)

What we gain

  1. Bundle size. dist/genie.js is currently ~305KB minified. zod v3 contributes meaningfully; @zod/mini could shave 10-30KB depending on tree-shake quality. Faster cold-start for the CLI.
  2. Validation perf. Every CLI invocation parses genie-config.yaml through a deeply-nested schema. v4's hot-path rewrites measurably reduce startup cost.
  3. Better DX. Cleaner inferred types across our 53-file footprint — fewer as casts, fewer z.infer<typeof X> workarounds.
  4. Maintainership. v3 is on a slow-moving deprecation path; future bug fixes and security patches will land in v4 first.
  5. Ecosystem alignment. Most modern TypeScript libraries (tRPC, drizzle, etc.) are migrating to or supporting v4.

Audit needed

# Discriminated union sites
grep -rn "discriminatedUnion\|z.union" src/

# z.record shorthand vs full
grep -rE "z\.record\([^,)]+\)" src/  # one-arg form may need migration

# coerce usage
grep -rn "z.coerce" src/

# error formatting consumers
grep -rn "ZodError\|\.format()\|\.flatten()\|\.errors\b" src/

# refine/transform chains
grep -rn "\.refine\(\|\.transform\(" src/

Each match needs a v4-vs-v3 behavior check.

Migration cost estimate

  • 2-3 days of focused work given the 53-file blast radius and the depth of usage in genie-config.ts + event schemas.
  • Risk: medium-high. Validation regressions are the worst kind — they're often silent until production data hits an edge case.
  • Strong test coverage helps: we have genie-config.test.ts and event-schema tests. Run those first, fix breakage, re-run.

Acceptance criteria

  • Bump zod to 4.3.6 in package.json (exact pin)
  • Run audit greps; migrate each affected pattern
  • Decide: stay on zod or split high-frequency hot paths to @zod/mini for bundle-size savings
  • Validate dist/genie.js size delta (target: ≤ current 305KB; ideally smaller)
  • bun test clean — particularly src/types/genie-config.test.ts and any event-schema tests
  • Smoke-test config loading on a real workspace

Context

  • Followup from PR fix/macos-tui-hardening (pinned packages for supply-chain hygiene post-CanisterWorm)
  • Companion follow-ups: commander v14, inquirer v8, uuid v14
  • Largest of the four — recommend doing this LAST after the other three land cleanly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:enhancementEnhancement - Improvement to existing functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions