santifer/career-ops · error · RangeError

capacity must be a finite number >= 1, got ${capacity}

Error message

capacity must be a finite number >= 1, got ${capacity}

What it means

The companion validation in createTokenBucket(): capacity must be a finite number >= 1 because it initializes the token count and burst size. Zero or negative capacity would mean the bucket can never hold a token, deadlocking all queued lookups; non-finite values break the token math.

Source

Thrown at providers/_dns-cache.mjs:139

 * @param {number} [options.ratePerMin] - Sustained calls per minute. Must be > 0.
 * @param {number} [options.capacity] - Burst size, in tokens.
 * @param {() => number} [options.now] - Clock source, injectable for tests.
 * @param {(fn: Function, ms: number) => void} [options.setTimer] - Timer, injectable for tests.
 * @returns {{ take: (fn: Function) => void, pending: number, stats: () => { delayed: number, waitedMs: number } }}
 */
export function createTokenBucket(options = {}) {
  const ratePerMin = options.ratePerMin ?? DEFAULT_LOOKUPS_PER_MIN;
  const capacity = options.capacity ?? DEFAULT_BURST;
  const now = options.now ?? Date.now;
  const setTimer = options.setTimer ?? setTimeout;

  // Caller-supplied, so validate rather than assert: a zero or negative rate
  // would make the refill interval Infinity and hang every queued lookup.
  if (!Number.isFinite(ratePerMin) || ratePerMin <= 0) {
    throw new RangeError(`ratePerMin must be a finite number > 0, got ${ratePerMin}`);
  }
  if (!Number.isFinite(capacity) || capacity < 1) {
    throw new RangeError(`capacity must be a finite number >= 1, got ${capacity}`);
  }

  const tokensPerMs = ratePerMin / 60_000;
  let tokens = capacity;
  let lastRefill = now();
  /** @type {{ fn: Function, queuedAt: number }[]} */
  const queue = [];
  let timerPending = false;
  let delayed = 0;
  let waitedMs = 0;

  function refill() {
    const t = now();
    tokens = Math.min(capacity, tokens + (t - lastRefill) * tokensPerMs);
    lastRefill = t;
  }

  function schedule() {

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Pass a finite integer >= 1 (a common value is equal to ratePerMin for one full minute of burst).
  2. Fix the config/env value feeding capacity; default it explicitly: capacity = Number(cfg.burst ?? 10).
  3. Don't use 0 to disable limiting — increase the rate instead or skip bucket creation entirely.
  4. Clamp before calling: capacity = Math.max(1, Math.floor(rawCapacity)) after checking Number.isFinite(rawCapacity).

Example fix

// before
createTokenBucket(60, 0); // RangeError
// after
createTokenBucket(60, 10);
Defensive patterns

Strategy: validation

Validate before calling

function toCapacity(raw, fallback = 10) {
  const n = Math.floor(Number(raw));
  return Number.isFinite(n) && n >= 1 ? n : fallback;
}
const bucket = createTokenBucket(60, toCapacity(process.env.DNS_BURST));

Type guard

function isValidCapacity(n) {
  return typeof n === 'number' && Number.isFinite(n) && n >= 1;
}

Try / catch

try {
  bucket = createTokenBucket(ratePerMin, capacity);
} catch (err) {
  if (err instanceof RangeError && err.message.includes('capacity')) {
    console.warn(`Bad capacity ${capacity}, falling back to 10`);
    bucket = createTokenBucket(ratePerMin, 10);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling createTokenBucket(rate, capacity) with 0, a negative value, NaN, or Infinity — e.g. a burst config of '0' meant to disable bursting, or capacity derived from an unset env var.

Common situations: Config where burst/capacity was left blank or set to 0 believing it disables the limiter; NaN from parsing a malformed number; an off-by-one where a minimum of 1 is required; copy-pasted tuning values.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/de551d39e531a3ab. Report an issue: GitHub.