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
- 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).
- Check the config/env value feeding the argument for typos, blank strings, or 0 meant as 'unlimited'.
- If you truly want no limiting, pass a very large finite rate (or a huge capacity) rather than 0.
- 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
- Default every rate env var explicitly and validate with Number.isFinite + > 0 at read time.
- Remember 0 does not mean 'unlimited' — use a large finite rate if you want effectively no limiting.
- Guard divisions that compute the rate (denominator can be 0 -> NaN).
- Unit-test bucket construction with edge inputs (0, NaN, Infinity, negative).
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
- capacity must be a finite number >= 1, got ${capacity}
- gmail: invalid days_back "${ctx?.settings?.days_back}" (must
- arbeitsagentur: entry "${entry.name || '(unnamed)'}" has no
- ashby: invalid URL: ${url}
- ashby: URL must use HTTPS: ${url}
AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01).
Data as JSON: /api/errors/f7c0b2a8c5289a66.
Report an issue: GitHub.