santifer/career-ops · error · RangeError

ratePerMin must be a finite number > 0, got ${ratePerMin}

Error message

ratePerMin must be a finite number > 0, got ${ratePerMin}

What it means

createTokenBucket() in providers/_dns-cache.mjs validates its ratePerMin argument with a RangeError. A zero, negative, NaN, or Infinity rate would make the token-refill interval Infinity, silently stalling every queued DNS lookup, so the factory rejects it up front rather than hanging at runtime.

Source

Thrown at providers/_dns-cache.mjs:136

 * Clock and timer are injectable so the tests are deterministic and offline.
 *
 * @param {object} [options] - Bucket tuning.
 * @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;

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Fix the rate source so it yields a finite positive number, e.g. const rate = Number(process.env.DNS_RATE_PER_MIN); if (!Number.isFinite(rate) || rate <= 0) rate = 60; (sane default).
  2. Check the config/env value feeding the argument for typos, blank strings, or 0 meant as 'unlimited'.
  3. If you truly want no limiting, pass a very large finite rate (or a huge capacity) rather than 0.
  4. Wrap the division that computes the rate and guard the denominator before calling createTokenBucket.

Example fix

// before
const bucket = createTokenBucket(Number(process.env.DNS_RATE), 10); // NaN if unset
// after
const rate = Number(process.env.DNS_RATE ?? 60);
if (!Number.isFinite(rate) || rate <= 0) throw new Error('DNS_RATE must be a positive number');
const bucket = createTokenBucket(rate, 10);
Defensive patterns

Strategy: validation

Validate before calling

function toPositiveRate(raw, fallback = 60) {
  const n = Number(raw);
  return Number.isFinite(n) && n > 0 ? n : fallback;
}
const bucket = createTokenBucket(toPositiveRate(process.env.DNS_RATE_PER_MIN), 10);

Type guard

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

Try / catch

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

Prevention

When it happens

Trigger: Calling createTokenBucket(ratePerMin, ...) with 0, a negative number, NaN (e.g. from parseFloat of a malformed env var), or Infinity — typically when the rate is computed from config/env like RATE_PER_MIN that is unset or unparsable.

Common situations: Missing or typo'd environment variable parsed with Number()/parseFloat returning NaN; a division producing NaN (e.g. requests/seconds where seconds is 0); hardcoded 0 to 'disable' the limiter (not supported — use Infinity capacity or bypass instead); copy-pasted config where the rate was left blank.

Related errors


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