ai/nanoid · error · RangeError

Wrong ID size

Error message

Wrong ID size

What it means

nanoid's `random(bytes)` coerces the requested byte count with `bytes |= 0` and throws a RangeError when the result is negative. This guards against NaN/negative/non-numeric inputs (and `valueOf` abuse) reaching `Buffer.allocUnsafe`, which would otherwise throw an opaque Node error. It means the size argument you passed to `random()` is not a valid non-negative integer.

Source

Thrown at index.js:21

export { urlAlphabet }

// `crypto.getRandomValues` rejects requests over 65536 bytes,
// so bigger buffers are filled by chunks.
const GET_RANDOM_LIMIT = 65536

function fillRandom(buffer) {
  let from = 0
  while (from < buffer.length) {
    let to = Math.min(from + GET_RANDOM_LIMIT, buffer.length)
    crypto.getRandomValues(buffer.subarray(from, to))
    from = to
  }
}

export function random(bytes) {
  // `|=` convert `bytes` to number to prevent `valueOf` abusing
  bytes |= 0
  if (bytes < 0) throw new RangeError('Wrong ID size')
  // `random()` is used rarely and not in hot paths, so it makes
  // a direct crypto call instead of using a byte pool.
  let buffer = Buffer.allocUnsafe(bytes)
  fillRandom(buffer)
  return buffer
}

export function customRandom(alphabet, defaultSize, getRandom) {
  // Random bytes are 0-255. `random % alphabet.length` can waste
  // that entropy by making some symbols more likely.
  //
  // `safeByteCutoff` will be divided by `alphabet.length` without remainder
  // fixing issue of broken distribution.
  //
  // Example: with 17 symbols, `safeByteCutoff` is 255.
  // Bytes 0-254 preserve entropy evenly: each symbol gets 15 source bytes.
  // Byte 255 would map to `0` again, making one symbol slightly more likely.
  // So we reject 255.

View on GitHub (pinned to 07a39d62d8)

Solutions

  1. Pass a non-negative integer to `random()`, e.g. `random(16)`.
  2. Clamp the value before calling: `bytes = Math.max(0, Math.trunc(Number(bytes)))`.
  3. Fix the upstream computation producing the negative value (e.g. guard subtraction results with Math.max(0, ...)).
  4. If the value comes from user input/config, validate it is a positive integer before passing it on.

Example fix

// before
const bytes = maxLen - currentLen
const buf = random(bytes) // throws if currentLen > maxLen
// after
const bytes = Math.max(0, maxLen - currentLen)
const buf = random(bytes)
Defensive patterns

Strategy: validation

Validate before calling

function isValidSize(n) {
  return Number.isInteger(Number(n)) && Number(n) >= 0
}
if (!isValidSize(bytes)) bytes = Math.max(0, Math.trunc(Number(bytes))) || 0
const buffer = random(bytes)

Type guard

function isNonNegativeInt(v) {
  return typeof v === 'number' && Number.isInteger(v) && v >= 0
}

Try / catch

let buffer
try {
  buffer = random(bytes)
} catch (e) {
  if (e instanceof RangeError && e.message === 'Wrong ID size') {
    buffer = random(16) // sane default
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Calling `random(-5)` directly; passing a fractional or huge negative number; passing an object whose `valueOf` returns a negative number or NaN (NaN |= 0 becomes 0, but a negative-returning valueOf triggers the throw); passing `undefined`-derived negative config values; passing a negative result of arithmetic (e.g. `maxSize - currentSize` when it goes negative).

Common situations: Config values read from env/JSON where a default of -1 is used as a sentinel; computing ID length from a subtraction that underflows; deserializing user input that contains a negative number; test code seeding lengths with negative placeholders; passing a string like '-21' that gets coerced to a negative number.


AI-assisted analysis of ai/nanoid@07a39d62d8 (2026-08-30). Data as JSON: /api/errors/212fa550ca924785. Report an issue: GitHub.