{"record":{"id":"158f7c0f88e1579a","repo":"remix-run/remix","slug":"limit-must-be-a-non-negative-safe-integer","errorCode":null,"errorMessage":"${limit} must be a non-negative safe integer","messagePattern":"(.+?) must be a non-negative safe integer","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"packages/route-pattern/src/lib/match/limits.ts","lineNumber":68,"sourceCode":"  actual: number\n}\n\nexport function createMatchWorkBudget(maximum: number): MatchWorkBudget {\n  return { maximum, actual: 0 }\n}\n\nexport function consumeMatchWork(budget: MatchWorkBudget, count: number): void {\n  let actual = budget.actual + count\n  if (!Number.isSafeInteger(actual)) actual = Number.MAX_SAFE_INTEGER\n  checkMatcherLimit('maxMatchWork', budget.maximum, actual)\n  budget.actual = actual\n}\n\nexport function resolveMatcherLimits(limits?: Partial<MatcherLimits>): MatcherLimits {\n  let result = { ...defaultMatcherLimits, ...limits }\n  for (let [limit, maximum] of Object.entries(result)) {\n    if (!Number.isSafeInteger(maximum) || maximum < 0) {\n      throw new RangeError(`${limit} must be a non-negative safe integer`)\n    }\n  }\n  return result\n}\n\nexport function checkMatcherLimit(\n  limit: keyof MatcherLimits,\n  maximum: number,\n  actual: number,\n): void {\n  if (actual > maximum) throw new MatcherResourceError({ limit, maximum, actual })\n}\n","sourceCodeStart":50,"sourceCodeEnd":81,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/route-pattern/src/lib/match/limits.ts#L50-L81","documentation":"The route-pattern matcher lets you cap resource usage via a limits object (max pattern size, nesting, work steps, etc.). resolveMatcherLimits merges defaults with user-supplied limits and requires every value to be a non-negative safe integer; anything else — negative, fractional, NaN, Infinity, or beyond Number.MAX_SAFE_INTEGER — throws a RangeError naming the offending limit key.","triggerScenarios":"Constructing a matcher with a limits option like { maxPatternLength: -1 }, { nestingDepth: 1.5 }, { matchWork: Infinity }, or { paramCount: NaN }. Any single bad entry fails construction immediately.","commonSituations":"Loading limits from env vars or config files as strings ('100' fails Number.isSafeInteger? no — strings are not numbers, so string configs throw); computing limits dynamically and passing 0/negative sentinels; typo'd keys are ignored but bad values are not.","solutions":["Coerce config-sourced values with Number() and validate them before constructing the matcher","Ensure each limit is a whole number >= 0 (omit keys you don't want to change)","Default unset values rather than passing -1 or Infinity as 'unlimited' — just leave the key out"],"exampleFix":"// before\nlet matcher = new RoutePatternMatcher({ limits: { maxPatternLength: Number.MAX_VALUE } })\n\n// after\nlet matcher = new RoutePatternMatcher({ limits: { maxPatternLength: 10_000 } })","handlingStrategy":"validation","validationCode":"for (let [k, v] of Object.entries(limits)) {\n  if (!Number.isSafeInteger(v) || v < 0) throw new Error(`Bad limit ${k}=${v}`)\n}","typeGuard":"function isNonNegativeSafeInteger(value: unknown): value is number {\n  return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n}","tryCatchPattern":null,"preventionTips":["Convert env/config strings with Number() and validate","Omit limits you don't need instead of passing sentinels"],"tags":["route-pattern","matcher","limits","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}