Skip to content

fix(client): take zod as a peer dependency - #3200

Merged
AlemTuzlak merged 2 commits into
mainfrom
contextablemark/albert-issue-3129
Oct 26, 2026
Merged

AlemTuzlak merged 2 commits into
mainfrom
contextablemark/albert-issue-3129

Conversation

@contextablemark

Copy link
Copy Markdown
Contributor

Closes #3129.

Problem

@ag-ui/client and @ag-ui/proto listed zod: ^3.25.76 as a direct dependency. An app that pins zod@4.6.6+ (the release that carries the CVE-2023-54404 size-gate fix) still ended up with a nested zod@3.25.76 under client and proto. Installing published 1.0.3 next to zod@4.6.7:

├─┬ @ag-ui/client@1.0.3
│ ├─┬ @ag-ui/core@1.0.3
│ │ └── zod@4.6.7 deduped
│ ├─┬ @ag-ui/proto@1.0.3
│ │ └── zod@3.25.76
│ └── zod@3.25.76
└── zod@4.6.7

Change

  • @ag-ui/client, @ag-ui/proto and @ag-ui/encoder now take zod as a required peer (^3.25.18 || ^4.0.0, the same range as @ag-ui/core), so they use the app's zod. zod stays a dev dependency for the workspace. The encoder needs the peer because it loads proto, which validates with core's schemas at runtime.
  • core/scripts/verify-zod-matrix.mjs: each cell now checks that every @ag-ui/* package uses the consumer's zod, not a nested copy.
  • The install instructions in the client, proto and encoder READMEs and in docs/migrating-to-1-0.mdx now add zod.
  • pnpm-lock.yaml regenerated. Besides the zod entries moving, pnpm worked out peer dependencies again for some unrelated packages. Notably, in the Dojo, @langchain/openai's @langchain/core peer now resolves to the 1.1.40 already in the lockfile, and in adk-middleware/js, @google/adk's optional MCP SDK now resolves against zod 3.25.76 instead of 4.4.3.

No source changes: the client already only uses the zod/v4 API, which both zod 3.25+ and 4.x export.

Compatibility

  • npm 7+ installs required peers automatically. With pnpm (strict mode), Yarn PnP or --legacy-peer-deps, projects without zod will get a missing-peer warning or error and need zod installed. This needs a changelog note at release.
  • Apps on zod 3 keep working. Zod 3 is still allowed, so the CVE is cleared only once the app itself is on zod ≥ 4.6.6.

Scope

Upgrading zod does not limit how much work AG-UI does on a very large payload. The upstream patch only short-circuits containers that declare a maximum size, and AG-UI's generated arrays have none. That follow-up is tracked in #3199.

Verification

  • nx run-many -t build,test,typecheck,lint for core, client, proto, encoder and adk-js: all pass.
  • node core/scripts/verify-zod-matrix.mjs: passes with zod 3.25.18, 3.25.76 and 4.6.7, plus the no-zod case, including the new single-copy check.
  • pnpm install --frozen-lockfile: passes.
  • Not run locally: the Dojo build; left to CI.

🤖 Generated with Claude Code

@ag-ui/client and @ag-ui/proto declared zod ^3.25.76 as a direct
dependency, so an application pinning zod >= 4.6.6 (the release with the
CVE-2023-54404 size-gate fix) still got a nested zod 3.25.76 beneath
them. Make zod a required peer (^3.25.18 || ^4.0.0, matching
@ag-ui/core) in client, proto and encoder, so every package uses the
application's zod.

The zod matrix now asserts every @ag-ui/* package resolves the
consumer's zod rather than a nested copy.

Closes #3129

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@3200

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@3200

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@3200

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@3200

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@3200

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@3200

@ag-ui/adk-js

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk-js@3200

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@3200

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@3200

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@3200

@ag-ui/antigravity

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/antigravity@3200

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@3200

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@3200

@ag-ui/claude-managed-agents

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-managed-agents@3200

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@3200

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@3200

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@3200

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@3200

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@3200

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@3200

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@3200

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@3200

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@3200

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@3200

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@3200

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@3200

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@3200

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@3200

commit: e05bbdc

@jaelliot

Copy link
Copy Markdown

A small matrix-strengthening suggestion, verified against 3b4b7b35f06fc22e3191bd4a824e95ececaca518 in a full checkout.

The current peer-resolution assertion compares only Zod version strings. A nested installation with the same version passes even though the SDK package does not resolve the consumer's physical installation. Comparing canonical zod/package.json paths with Node's realpathSync pins the stronger peer-layout guarantee while keeping the existing version checks and runtime smoke tests.

Reproduction on the actual matrix: immediately after each consumer install, copy its node_modules/zod directory to node_modules/@ag-ui/encoder/node_modules/zod (a real second copy, not a symlink). With otherwise identical builds/installations:

  • Original matrix passes all cells, despite the duplicate same-version installation.
  • Patched matrix rejects the first cell with @ag-ui/encoder resolved a different zod installation and both canonical paths.
  • Unmodified install layout with the patch passes Zod 3.25.18, 3.25.76 and 4.6.7, CJS/ESM core smoke, actual client pipeline smoke, and the no-Zod core-entry cell.

Node 22.23.1, pinned pnpm 10.33.4, lifecycle scripts disabled; no consumer overrides. Also pass:

  • pnpm exec nx run-many -p @ag-ui/core @ag-ui/client @ag-ui/proto @ag-ui/encoder -t build lint typecheck --parallel=2
  • Same projects with -t test --parallel=2 --maxWorkers=2: 138 test files / 2,047 tests pass.
  • node --check sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs and git diff --check.

This asserts physical package resolution in the npm consumer matrix, not universal module identity across formats/bundlers/package managers. In particular, the existing Zod v4 cross-copy tests show instanceof can work across copies; this patch does not claim all duplicate copies break validation. Nor does it make permitted Zod 3 versions patched/safe.

Please feel free to fold this incremental patch into this PR. Happy to coordinate a branch-targeted contribution if preferred; no competing PR opened. Codex assisted with implementation and verification.

diff --git a/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs b/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs
index 2b0c472c..268e331f 100644
--- a/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs
+++ b/sdks/typescript/packages/core/scripts/verify-zod-matrix.mjs
@@ -6,7 +6,7 @@
 // pinned zod (an npm override forces the package's own dependency onto that
 // major) and runs the same smoke through require() and import.
 import { execFileSync } from "node:child_process";
-import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
+import { mkdtempSync, writeFileSync, rmSync, realpathSync } from "node:fs";
 import { tmpdir } from "node:os";
 import { basename, dirname, join, resolve } from "node:path";
 import { fileURLToPath } from "node:url";
@@ -147,6 +147,10 @@ try {
       }
       // The peer guarantee: every package resolves the consumer's zod, so an application
       // that pins a patched zod gets no older copy nested beneath @ag-ui/*.
+      const consumerZodPath = realpathSync(execFileSync(
+        "node", ["-p", "require.resolve('zod/package.json')"],
+        { cwd: consumer, encoding: "utf8" },
+      ).trim());
       for (const name of ["core", ...SIBLINGS]) {
         const resolved = execFileSync(
           "node",
@@ -159,6 +163,14 @@ try {
         if (resolved !== installed) {
           throw new Error(`@ag-ui/${name} resolved zod ${resolved}, not the consumer's ${installed}`);
         }
+        const resolvedPath = realpathSync(execFileSync(
+          "node",
+          ["-p", `require.resolve("zod/package.json", { paths: [require.resolve("@ag-ui/${name}/package.json")] })`],
+          { cwd: consumer, encoding: "utf8" },
+        ).trim());
+        if (resolvedPath !== consumerZodPath) {
+          throw new Error(`@ag-ui/${name} resolved a different zod installation: ${resolvedPath}, not ${consumerZodPath}`);
+        }
       }
       console.log(`\n== zod@${installed} ==`);
       run("node", ["smoke.cjs"], consumer);

mme
mme previously approved these changes Oct 24, 2026
@AlemTuzlak
AlemTuzlak merged commit 2f3a48d into main Oct 26, 2026
58 checks passed
@AlemTuzlak
AlemTuzlak deleted the contextablemark/albert-issue-3129 branch October 26, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: @ag-ui/client depends on zod ^3.25.76, affected by CVE-2023-54404

4 participants