can1357/oh-my-pi · error · OmpTypeError
${kind} length must be a nonnegative safe integer
Error message
${kind} length must be a nonnegative safe integer What it means
The zod-compat layer's `.min()`/`.max()` length methods validate the bound via `lengthBound`. For string and array schemas the bound must be a nonnegative safe integer; NaN, Infinity, negative values, or non-integers throw `<kind> length must be a nonnegative safe integer`.
Source
Thrown at packages/omptype/src/zod.ts:82
hasDefault: false,
run: value => value,
};
return type.raw(embedded) as unknown as Decoratable<Out>;
}
function restrictBase<Out>(source: Decoratable<Out>, ir: IR): Decoratable<Out> {
let next = source.hasSteps
? schemaFromIR<Out>({ k: "morph", input: ir, fn: value => source(value) })
: schemaFromIR<Out>(ir);
if (source.ir.desc !== undefined) next = next.describe(source.ir.desc);
if (source.hasDefault) next = next.default(source.defaultValue as Out | (() => Out));
return next;
}
function lengthBound(kind: "min" | "max", schema: Decoratable<unknown>, bound: number): void {
if (schema.ir.k !== "string" && schema.ir.k !== "array") return;
if (!Number.isSafeInteger(bound) || bound < 0) {
throw new OmpTypeError(`${kind} length must be a nonnegative safe integer`);
}
}
function refinementMessage(messageOrOptions: string | RefineOptions | undefined): string {
if (typeof messageOrOptions === "string") return messageOrOptions;
return messageOrOptions?.message ?? messageOrOptions?.error ?? "valid (refinement failed)";
}
function isStringKeyIR(ir: IR): boolean {
switch (ir.k) {
case "string":
return true;
case "lit":
return typeof ir.v === "string";
case "union":
return ir.members.length > 0 && ir.members.every(isStringKeyIR);
case "sub":
return isStringKeyIR(ir.schema.ir);View on GitHub (pinned to 9690622007)
Solutions
- Validate the bound first: `Number.isSafeInteger(bound) && bound >= 0`; round with `Math.max(0, Math.floor(x))` if deriving it.
- If the constraint should be unbounded, omit the `.min()`/`.max()` call rather than passing Infinity.
- Fix the source computation producing NaN/fractional values before it reaches the schema builder.
Example fix
// before const minLen = Number(config.minLen); // NaN schema.min(minLen); // after const raw = Number(config.minLen); if (Number.isSafeInteger(raw) && raw >= 0) schema.min(raw);
Defensive patterns
Strategy: validation
Validate before calling
function nonNegInt(n: number): number {
if (!Number.isSafeInteger(n) || n < 0) throw new RangeError(`length bound must be a nonnegative safe integer, got ${n}`);
return n;
}
// use: schema.min(nonNegInt(rawMin)) Type guard
function isLengthBound(v: unknown): v is number {
return typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
} Try / catch
try {
const s = z.string().min(rawMin);
} catch (err) {
if (err instanceof Error && err.message.includes('must be a nonnegative safe integer')) {
throw new Error(`Invalid length bound ${rawMin} from config`);
}
throw err;
} Prevention
- Sanitize config-derived bounds with Number.isSafeInteger && >= 0 before chaining .min/.max.
- Floor/round derived values (Math.max(0, Math.floor(x))) before use.
- Omit the bound rather than passing Infinity for 'unlimited'.
When it happens
Trigger: `z.string().min(-1)`, `z.string().max(Infinity)`, `z.array(el).min(0.5)`, or bounds computed from variables (e.g. `Math.sqrt(-1)`, parsed config) yielding NaN/Infinity/fractions.
Common situations: Reading limits from unvalidated config/env where the parse fails to NaN; arithmetic like `Math.floor(total/2)` on undefined; copying zod v3 code that tolerated non-integer bounds.
Related errors
- number min must not be NaN
- ${name} must be finite
- invalid regular expression pattern ${JSON.stringify(opts.pat
- multipleOf must be greater than zero
- object mode requires an object schema
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e36da652a1824ec0.
Report an issue: GitHub.