santifer/career-ops · error · RangeError

Reservation count must be an integer from 1 to

Error message

Reservation count must be an integer from 1 to ${MAX_COUNT}

What it means

reserveReportNumbers(count) validates its count argument before doing any work: it must be an integer between 1 and MAX_COUNT (50). A RangeError is thrown immediately when count is a non-integer (e.g. a float or a numeric string coming from CLI parsing), zero, negative, or greater than 50. This is an upfront argument guard so the allocator never attempts an impossible reservation.

Solutions

  1. Clamp and validate count before calling: const n = Math.floor(Number(raw)); if (Number.isInteger(n) && n >= 1 && n <= 50) await reserveReportNumbers(n);
  2. When you need more than 50 IDs, make multiple sequential reserveReportNumbers calls (e.g. two calls of 40) instead of one oversized call.
  3. Convert CLI/env strings with Number() or parseInt(raw, 10) and check Number.isInteger before passing them.
  4. Default to the documented count=1 when the input is not a usable integer.

Example fix

// before
const count = process.env.FANOUT;
const ids = await reserveReportNumbers(count); // throws: '8' is not an integer
// after
const count = Number.parseInt(process.env.FANOUT ?? '1', 10);
if (!Number.isInteger(count) || count < 1 || count > 50) {
  throw new Error(`FANOUT must be an integer 1-50, got ${process.env.FANOUT}`);
}
const ids = await reserveReportNumbers(count);
Defensive patterns

Strategy: validation

Validate before calling

function toCount(raw) {
  const n = Number.parseInt(raw, 10);
  if (!Number.isInteger(n) || n < 1 || n > 50) {
    throw new RangeError(`count must be an integer 1-50, got ${raw}`);
  }
  return n;
}
const ids = await reserveReportNumbers(toCount(userInput));

Type guard

const isValidCount = (v) => Number.isInteger(v) && v >= 1 && v <= 50;

Try / catch

let ids;
try {
  ids = await reserveReportNumbers(count);
} catch (err) {
  if (err instanceof RangeError && /Reservation count/.test(err.message)) {
    ids = await reserveReportNumbers(1); // safe default
  } else throw err;
}

Prevention

When it happens

Trigger: Calling reserveReportNumbers(0), reserveReportNumbers(-3), reserveReportNumbers(51), reserveReportNumbers(2.5), or passing a string parsed from --count without Number conversion (Number.isInteger('8') is false). Also happens when a config value like a batch-size env var is NaN or undefined and defaults are bypassed.

Common situations: CLI flag mishandling: `--count` read as a raw string or parseFloat result like 8.5. Config mistakes: an env var CAREER_OPS_BATCH_SIZE set to '0' or '100'. Off-by-one math: computing count as numbers.length when the array is empty (0). Automation scripts fanning out more than the documented 50-worker cap.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/26a4ed6fd9adbb92. Report an issue: GitHub.

Appendix: source

Thrown at reserve-report-num.mjs:172

  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
  try {
    process.kill(pid, 0);
    return true;
  } catch (err) {
    return err?.code === 'EPERM';
  }
}

/**
 * Reserve one or more contiguous report IDs.
 *
 * @param {number} [count=1] Number of IDs to reserve (1-50).
 * @param {object} [options] Path and lock overrides.
 * @returns {Promise<number[]>} Reserved numeric IDs.
 */
export async function reserveReportNumbers(count = 1, options = {}) {
  if (!Number.isInteger(count) || count < 1 || count > MAX_COUNT) {
    throw new RangeError(`Reservation count must be an integer from 1 to ${MAX_COUNT}`);
  }

  const reportsDir = reportsDirFor(options);
  const trackerPath = trackerPathFor(options);
  mkdirSync(reportsDir, { recursive: true });

  const lock = await acquireTrackerLock(trackerLockDirFor(trackerPath), {
    timeoutMs: Number(process.env.CAREER_OPS_TRACKER_LOCK_TIMEOUT_MS) || 60_000,
    retryMs: Number(process.env.CAREER_OPS_TRACKER_LOCK_RETRY_MS) || 75,
    staleMs: Number(process.env.CAREER_OPS_TRACKER_LOCK_STALE_MS) || 10 * 60_000,
    tracker: trackerPath,
    ...options.lockOptions,
  });

  try {
    let occupied = collectOccupied(reportsDir, trackerPath);
    let base = highestNumber(occupied) + 1;
    const token = randomUUID();

View on GitHub (pinned to aac998c7ed)