can1357/oh-my-pi · error · OmpTypeError

number min must not be NaN

Error message

number min must not be NaN

What it means

In the zod-compat layer, `.min()` applied to a number schema rejects NaN bounds with `number min must not be NaN` (the length-bound safe-integer rule does not apply to numbers, but NaN is still meaningless as a minimum). It is thrown at builder time, before any value is validated.

Source

Thrown at packages/omptype/src/zod.ts:139

			const result = schema(value);
			if (!(result instanceof type.errors)) return { success: true, data: result };
			return {
				success: false,
				error: {
					message: result.summary,
					issues: result.map(issue => ({ path: [...issue.path], message: issue.problem })),
				},
			};
		},
		min(bound: number): ZodLikeSchema<Out> {
			const ir = schema.ir;
			if (ir.k === "string" || ir.k === "array") {
				lengthBound("min", schema, bound);
				const min = ir.min === undefined ? bound : Math.max(ir.min, bound);
				return next(restrictBase(schema, { ...ir, min }));
			}
			if (ir.k === "number") {
				if (Number.isNaN(bound)) throw new OmpTypeError("number min must not be NaN");
				if (ir.min !== undefined && ir.min >= bound) return next(restrictBase(schema, ir));
				return next(restrictBase(schema, { ...ir, min: bound, xmin: false }));
			}
			throw new OmpTypeError(`cannot apply min to ${ir.k}`);
		},
		max(bound: number): ZodLikeSchema<Out> {
			const ir = schema.ir;
			if (ir.k === "string" || ir.k === "array") {
				lengthBound("max", schema, bound);
				const max = ir.max === undefined ? bound : Math.min(ir.max, bound);
				return next(restrictBase(schema, { ...ir, max }));
			}
			if (ir.k === "number") {
				if (Number.isNaN(bound)) throw new OmpTypeError("number max must not be NaN");
				if (ir.max !== undefined && ir.max <= bound) return next(restrictBase(schema, ir));
				return next(restrictBase(schema, { ...ir, max: bound, xmax: false }));
			}
			throw new OmpTypeError(`cannot apply max to ${ir.k}`);

View on GitHub (pinned to 9690622007)

Solutions

  1. Validate the bound before calling: `if (!Number.isNaN(min)) schema.min(min)`.
  2. Fix the upstream computation that yields NaN (default the config value, guard division).
  3. Omit the `.min()` call when no numeric lower bound is intended.

Example fix

// before
num.min(Number(opts.minimum)); // NaN when opts.minimum undefined
// after
const m = Number(opts.minimum);
if (Number.isFinite(m)) num.min(m);
Defensive patterns

Strategy: validation

Validate before calling

function finiteMin(n: number | undefined): number | undefined {
  if (n === undefined) return undefined;
  if (Number.isNaN(n)) throw new RangeError('number min must not be NaN');
  return n;
}
// use: num.min(finiteMin(rawMin)!)

Type guard

function isNumericMin(v: unknown): v is number {
  return typeof v === "number" && !Number.isNaN(v);
}

Try / catch

try {
  const s = z.number().min(rawMin);
} catch (err) {
  if (err instanceof Error && err.message === 'number min must not be NaN') {
    throw new Error(`Minimum limit config value is not a number`);
  }
  throw err;
}

Prevention

When it happens

Trigger: `z.number().min(Number.NaN)` or `z.number().min(someVar)` where `someVar` is NaN — commonly `Number(undefined)`, a failed `parseFloat` of config, or `0/0` in a computed default.

Common situations: Deriving minimum limits from env/config that is unset or unparsable; spreading optional option objects where the min field was computed earlier and became NaN.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c4bd04a73b772b17. Report an issue: GitHub.