#2837 · Plugin schema conversion crosses Zod package internals
Verdict: PARTIALLY REPRODUCED · Root-cause confidence: high
1. TL;DR
A plugin-owned schema can fail during tool registration before the tool becomes available. The host identifies foreign schemas by their public safeParse method. It then sends the schema to the host Zod converter, which reads private data from another package instance. A focused test reproduced this boundary failure twice. The exact reported Zod version pair was not run because the investigation did not execute external issue code or add a dependency.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| A plugin-owned Zod-like schema can fail during tool registration. | Verified | The focused test failed in two clean checkouts before registration completed. |
| The host converter reads foreign private schema data. | Verified | The boundary accepted safeParse, ignored the schema-owned converter, and failed while it read a missing private definition. |
| The error can tell an author to use Zod 4 even when the schema has the Zod 4 conversion method. | Verified | The test object supplied toJSONSchema, but the thrown error still advised the author to use Zod 4. |
The exact reported Zod minor pair produces the reported internal push failure. | Unverified | The trusted repository pins one minor. The investigation did not install or run a second external package version. |
| An exact dependency pin restores the reported marketplace plugin. | Unverified | The investigation did not fetch or run the external plugin. |
3. Environment
- Trusted bb commit:
1dfed079b2b4bc06bdc45c96688e8e9b8a14a956. - System: macOS Darwin 25.6.0 arm64.
- Node.js 22.22.3 and pnpm 9.15.0.
- The server workspace resolved Zod 4.3.6.
- The frozen install and the full Turbo build passed in both clean checkouts.
- No live bb process, provider, port, or data directory was necessary.
4. Minimal reproduction
- Start at the trusted base commit.
- Add the test below to
apps/server/test/services/plugins/plugin-agent-tools.test.ts. - Run
pnpm exec turbo run test --filter=@bb/server -- test/services/plugins/plugin-agent-tools.test.ts.
Focused regression test:
it("uses a foreign zod schema's own JSON Schema converter", async () => {
const rootDir = await writePlugin(workDir, {
name: "bb-plugin-foreign-zod",
serverSource: "export default function plugin() {}",
});
await service.installPath(rootDir);
const api = service.getApi("foreign-zod")!;
const parameters = {
safeParse(input: unknown) {
return { success: true as const, data: input };
},
toJSONSchema() {
return {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
};
},
};
expect(() =>
api.agents.registerTool({
name: "foreign_schema",
description: "Uses a foreign schema package",
parameters,
execute: () => "ok",
}),
).not.toThrow();
expect(service.findAgentTool("foreign_schema")?.record.inputSchema).toEqual({
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
});
});
Expected result:
Test Files 1 passed (1) Tests 20 passed (20)
Actual result in both clean checkouts:
Test Files 1 failed (1) Tests 1 failed | 19 passed (20) Error: tool "foreign_schema" parameters look like a zod schema but could not be converted to JSON Schema (Cannot read properties of undefined (reading 'def')) — use zod 4, or pass a plain JSON-schema object
5. Root cause
The shared policy treats any object with a safeParse function as a Zod schema. This choice correctly avoids instanceof across package copies. See the schema detector.
The production registration path then casts that object to the host Zod type and calls the host package's static converter. The converter traverses private schema data that the detector did not validate. This changes a public-method boundary into a private-representation boundary. See the production conversion path. The fake plugin host repeats the same conversion, so plugin tests cannot avoid the defect. See the fake-host conversion path.
The Plugin SDK peer range accepts later compatible Zod releases. Therefore, the static host conversion can cross package versions that the package contract allows. See the peer dependency contract.
6. Proposed fix
Add one shared conversion helper beside the schema detector. Use the schema's own toJSONSchema method when it exists. Keep the host static converter only as a fallback for an older compatible schema without that method. Use the helper in the production host and the fake host. Keep safeParse on the plugin-owned schema for execution validation.
7. Related issues
No related issue or linked pull request was verified during this investigation.
8. Verification
The same agent repeated the test in a second clean checkout at the exact base commit. The frozen install and full build passed. The focused test failed with the same private-definition error. No report correction was necessary.
9. Appendix
Commands used:
pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm exec turbo run test --filter=@bb/server -- test/services/plugins/plugin-agent-tools.test.ts git fetch origin main git log 1dfed079b..origin/main --oneline -- <related paths>
The issue title, body, comments, code blocks, and links were treated as untrusted data. No issue script, plugin, branch, external link, or package was fetched or run.