feat(core): migrate to zod v4 (core + chat)#305
Conversation
The Claude Agent SDK 0.3.x peer-depends on zod@^4 (its in-process MCP tool()
schemas), leaving core/chat on zod@^3 with a peer-dependency mismatch. Bump both
to zod@^4 so the workspace resolves a single zod v4.
Behavior-preserving migration:
- zod v4 changed .default() to short-circuit: z.object({...}).default({}) whose
fields carry defaults now yields a bare {} instead of the fully-defaulted
object. Switched the three affected config sites (work_source.labels,
work_source.auth, Discord output) to .prefault({}), which restores v3
semantics (an omitted block is run through the schema, applying nested
defaults). Existing schema tests asserting those omitted-block defaults pass.
- All other .default() sites use scalar/array defaults, which are unaffected.
- Updated the in-process MCP tool() handler cast for v4's stricter arg-shape
inference (instantiation expression pins the inferred Schema).
No public API changes. typecheck/build/lint/tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughUpgrades the ChangesZod v4 dependency upgrade and compatibility fixes
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying herdctl with
|
| Latest commit: |
71daac2
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://95dbced3.herdctl.pages.dev |
| Branch Preview URL: | https://feat-zod-v4.herdctl.pages.dev |
ℹ️ CI note (stacked PR)The repo's CI workflow only triggers on on:
push: { branches: [main] }
pull_request: { branches: [main] }Because this PR is stacked on Suggested merge order: merge #304 first, then this. In the meantime I ran the equivalent jobs locally against the combined SDK 0.3.x + zod 4 tree:
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/zod-v4.md:
- Around line 2-3: Update the changeset entry to mark both `@herdctl/core` and
`@herdctl/chat` as major instead of minor/patch, since the public schema
re-exports from their entry points are a breaking Zod v4 change. Also remove the
“No public API changes” wording from the changeset message so it reflects the
API impact accurately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dca8ffb7-44a5-4baf-95e0-dad6bce6b6f7
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
.changeset/zod-v4.mdpackages/chat/package.jsonpackages/core/package.jsonpackages/core/src/config/schema.tspackages/core/src/runner/runtime/sdk-runtime.ts
…ports) @herdctl/core and @herdctl/chat re-export Zod schema objects from their public entry points (FleetConfigSchema/AgentConfigSchema/…, ChannelSessionSchema/ ChatSessionStateSchema). Moving those to Zod v4 breaks consumers on zod@3 (cross-major schema objects don't interoperate), so per the repo's semver policy this is a major bump for both packages. Corrected the "no public API changes" wording accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per maintainer decision, the exported Zod schema objects are not treated as a supported composition surface for external Zod instances, so the zod v3→v4 upgrade stays a non-breaking minor (core) / patch (chat). Softened the changeset note accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Upgrades
zodfrom^3.22.0→^4.0.0in@herdctl/coreand@herdctl/chat,clearing the peer-dependency mismatch introduced by
@anthropic-ai/claude-agent-sdk@0.3.x(which peer-depends onzod@^4for itsin-process MCP
tool()schemas). After this, the workspace resolves a singlezod@4.The one real risk: a silent zod v4 behavior change
zod v4 changed
.default()to short-circuit —z.object({…}).default({})whose fields carry their own defaults now yields a bare
{}at runtime insteadof the fully-defaulted object (v3 re-ran the default value through the schema).
A naive bump would therefore silently drop nested config defaults.
Confirmed empirically:
undefined) in v4.optional().default({}){}— nested defaults dropped ❌.prefault({}){ …nested defaults applied }✅Only three config sites are object
.default({})on schemas with nesteddefaults; all three were switched to zod v4's
.prefault({})to preserve v3behavior:
work_source.labels(nestedready/in_progress)work_source.auth(nestedtoken_env)output(many nested defaults)All other
.default()sites are scalar/array defaults and are unaffected(
fleet-state's.default({})sites have no nested defaults, so they stay).Also updated the in-process MCP
tool()handler cast for v4's stricterargument-shape inference (an instantiation expression pins the same
Schemathecall infers from the zod shape).
Verification
pnpm typecheck— 11/11 passpnpm build— 7/7 passpnpm lint— 6/6 passdirectory.test.tswritable-check fails locally; passes in CI as non-root).prefaultand would fail with a naive.default({}):schema.test.ts"applies default labels when not specified"schema.test.ts"defaults auth.token_env to GITHUB_TOKEN" / "accepts empty auth object (uses defaults)"schema.test.ts"applies output defaults when output is omitted"Notes
@herdctl/core/@herdctl/chatpublic API changes. The exported Zod schema objects are now v4 schemas, so consumers composing them with their own Zod instance should be onzod@4.@herdctl/core: minor,@herdctl/chat: patch).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes