remix-run/remix · error · RangeError

${limit} must be a non-negative safe integer

Error message

${limit} must be a non-negative safe integer

What it means

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.

Source

Thrown at packages/route-pattern/src/lib/match/limits.ts:68

  actual: number
}

export function createMatchWorkBudget(maximum: number): MatchWorkBudget {
  return { maximum, actual: 0 }
}

export function consumeMatchWork(budget: MatchWorkBudget, count: number): void {
  let actual = budget.actual + count
  if (!Number.isSafeInteger(actual)) actual = Number.MAX_SAFE_INTEGER
  checkMatcherLimit('maxMatchWork', budget.maximum, actual)
  budget.actual = actual
}

export function resolveMatcherLimits(limits?: Partial<MatcherLimits>): MatcherLimits {
  let result = { ...defaultMatcherLimits, ...limits }
  for (let [limit, maximum] of Object.entries(result)) {
    if (!Number.isSafeInteger(maximum) || maximum < 0) {
      throw new RangeError(`${limit} must be a non-negative safe integer`)
    }
  }
  return result
}

export function checkMatcherLimit(
  limit: keyof MatcherLimits,
  maximum: number,
  actual: number,
): void {
  if (actual > maximum) throw new MatcherResourceError({ limit, maximum, actual })
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Coerce config-sourced values with Number() and validate them before constructing the matcher
  2. Ensure each limit is a whole number >= 0 (omit keys you don't want to change)
  3. Default unset values rather than passing -1 or Infinity as 'unlimited' — just leave the key out

Example fix

// before
let matcher = new RoutePatternMatcher({ limits: { maxPatternLength: Number.MAX_VALUE } })

// after
let matcher = new RoutePatternMatcher({ limits: { maxPatternLength: 10_000 } })
Defensive patterns

Strategy: validation

Validate before calling

for (let [k, v] of Object.entries(limits)) {
  if (!Number.isSafeInteger(v) || v < 0) throw new Error(`Bad limit ${k}=${v}`)
}

Type guard

function isNonNegativeSafeInteger(value: unknown): value is number {
  return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/158f7c0f88e1579a. Report an issue: GitHub.