Repro
package.json: { "perry": { "compilePackages": ["zod"], "allow": { "compilePackages": ["zod"] } } }
import { z } from "zod";
console.log(z.string().min(2).parse("hello"));
Perry:
TypeError: Cannot read properties of undefined (reading 'onattach')
at <anonymous>
Node (--experimental-strip-types): hello
Any schema with a check (.min, .max, .length, .regex, .email, numeric .gt/.lt, …) hits this — z.string()/z.object().parse() without checks works.
Analysis
In zod's $ZodType initializer (node_modules/zod/src/v4/core/schemas.ts):
const checks = [...(inst._zod.def.checks ?? [])];
for (const ch of checks) {
for (const fn of ch._zod.onattach) { fn(inst); } // ch._zod is undefined
}
A check instance in def.checks comes back without _zod. The check is built by new $ZodCheckMinLength({...}) (a $constructor that installs _zod via Object.defineProperty(inst, "_zod", { value, enumerable:false })), threaded through inst.check() → util.mergeDefs(def, { checks:[...] }) (which round-trips via Object.getOwnPropertyDescriptors + Object.defineProperties) → core.clone().
Not reproducible standalone: a faithful standalone model of the entire $constructor + $ZodCheck.init + onattach.push + mergeDefs (getOwnPropertyDescriptors/defineProperties round-trip) + [...def.checks] spread chain runs correctly in Perry (byte-matches Node). So the _zod loss is specific to the cross-module perry.compilePackages compilation of real zod — a check instance constructed in core/checks.ts and read back through core/schemas.ts / core/util.ts loses its non-enumerable _zod property somewhere across the module boundary.
Context
Follow-up to discussion #3438. The core object-parse crash (reading '_zod' of undefined) is fixed by #4697 (Object.defineProperty attribute retention); with that fix, z.object() / nested / array / boolean / optional parsing works. This checks-path bug and the safeParse error-path bug (separate issue) remain. Refs #793.
Repro
package.json:{ "perry": { "compilePackages": ["zod"], "allow": { "compilePackages": ["zod"] } } }Perry:
Node (
--experimental-strip-types):helloAny schema with a check (
.min,.max,.length,.regex,.email, numeric.gt/.lt, …) hits this —z.string()/z.object().parse()without checks works.Analysis
In zod's
$ZodTypeinitializer (node_modules/zod/src/v4/core/schemas.ts):A check instance in
def.checkscomes back without_zod. The check is built bynew $ZodCheckMinLength({...})(a$constructorthat installs_zodviaObject.defineProperty(inst, "_zod", { value, enumerable:false })), threaded throughinst.check()→util.mergeDefs(def, { checks:[...] })(which round-trips viaObject.getOwnPropertyDescriptors+Object.defineProperties) →core.clone().Not reproducible standalone: a faithful standalone model of the entire
$constructor+$ZodCheck.init+onattach.push+mergeDefs(getOwnPropertyDescriptors/defineProperties round-trip) +[...def.checks]spread chain runs correctly in Perry (byte-matches Node). So the_zodloss is specific to the cross-moduleperry.compilePackagescompilation of real zod — a check instance constructed incore/checks.tsand read back throughcore/schemas.ts/core/util.tsloses its non-enumerable_zodproperty somewhere across the module boundary.Context
Follow-up to discussion #3438. The core object-parse crash (
reading '_zod' of undefined) is fixed by #4697 (Object.definePropertyattribute retention); with that fix,z.object()/ nested / array / boolean / optional parsing works. This checks-path bug and thesafeParseerror-path bug (separate issue) remain. Refs #793.