{"record":{"id":"e36da652a1824ec0","repo":"can1357/oh-my-pi","slug":"kind-length-must-be-a-nonnegative-safe-integer","errorCode":null,"errorMessage":"${kind} length must be a nonnegative safe integer","messagePattern":"(.+?) length must be a nonnegative safe integer","errorType":"exception","errorClass":"OmpTypeError","httpStatus":null,"severity":"error","filePath":"packages/omptype/src/zod.ts","lineNumber":82,"sourceCode":"\t\thasDefault: false,\n\t\trun: value => value,\n\t};\n\treturn type.raw(embedded) as unknown as Decoratable<Out>;\n}\n\nfunction restrictBase<Out>(source: Decoratable<Out>, ir: IR): Decoratable<Out> {\n\tlet next = source.hasSteps\n\t\t? schemaFromIR<Out>({ k: \"morph\", input: ir, fn: value => source(value) })\n\t\t: schemaFromIR<Out>(ir);\n\tif (source.ir.desc !== undefined) next = next.describe(source.ir.desc);\n\tif (source.hasDefault) next = next.default(source.defaultValue as Out | (() => Out));\n\treturn next;\n}\n\nfunction lengthBound(kind: \"min\" | \"max\", schema: Decoratable<unknown>, bound: number): void {\n\tif (schema.ir.k !== \"string\" && schema.ir.k !== \"array\") return;\n\tif (!Number.isSafeInteger(bound) || bound < 0) {\n\t\tthrow new OmpTypeError(`${kind} length must be a nonnegative safe integer`);\n\t}\n}\n\nfunction refinementMessage(messageOrOptions: string | RefineOptions | undefined): string {\n\tif (typeof messageOrOptions === \"string\") return messageOrOptions;\n\treturn messageOrOptions?.message ?? messageOrOptions?.error ?? \"valid (refinement failed)\";\n}\n\nfunction isStringKeyIR(ir: IR): boolean {\n\tswitch (ir.k) {\n\t\tcase \"string\":\n\t\t\treturn true;\n\t\tcase \"lit\":\n\t\t\treturn typeof ir.v === \"string\";\n\t\tcase \"union\":\n\t\t\treturn ir.members.length > 0 && ir.members.every(isStringKeyIR);\n\t\tcase \"sub\":\n\t\t\treturn isStringKeyIR(ir.schema.ir);","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/omptype/src/zod.ts#L64-L100","documentation":"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`.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst minLen = Number(config.minLen); // NaN\nschema.min(minLen);\n// after\nconst raw = Number(config.minLen);\nif (Number.isSafeInteger(raw) && raw >= 0) schema.min(raw);","handlingStrategy":"validation","validationCode":"function nonNegInt(n: number): number {\n  if (!Number.isSafeInteger(n) || n < 0) throw new RangeError(`length bound must be a nonnegative safe integer, got ${n}`);\n  return n;\n}\n// use: schema.min(nonNegInt(rawMin))","typeGuard":"function isLengthBound(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0;\n}","tryCatchPattern":"try {\n  const s = z.string().min(rawMin);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('must be a nonnegative safe integer')) {\n    throw new Error(`Invalid length bound ${rawMin} from config`);\n  }\n  throw err;\n}","preventionTips":["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'."],"tags":["omptype","zod-compat","length-bound","invalid-argument"],"backgroundTag":"invalid-length-constraint","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}