santifer/career-ops · critical · RangeError

No safe report-number range remains

Error message

No safe report-number range remains

What it means

After scanning occupied report numbers (report files, sentinels, tracker rows/links), the allocator computes base = maxOccupied + 1 and the range end. If base or end exceeds Number.MAX_SAFE_INTEGER, no safe contiguous range exists, so it throws this RangeError rather than silently allocating unsafe numeric IDs. In practice this fires only when occupied numbers are astronomically large.

Solutions

  1. Inspect reports/ for files whose names start with huge digit prefixes and move/rename them out of the directory (they are treated as occupied report numbers).
  2. Set options.reportsDir / CAREER_OPS_REPORTS_DIR to the correct dedicated reports directory instead of one polluted with timestamp-named files.
  3. Check data/applications.md for rows with absurd num values or report links like [20250601](reports/2025-06-01...) and fix them.
  4. Note the allocator already excludes bare date files (YYYY-MM-DD.md) but not timestamp-prefixed names — rename those files to not start with digits.

Example fix

// before
$ cp /tmp/scan-2026-09-16T10-00-00.md reports/   // name starts with digits -> occupied 2026...
// after
$ mv /tmp/scan-2026-09-16T10-00-00.md reports/scan-digest-2026-09-16.md // no leading digits
Defensive patterns

Strategy: validation

Validate before calling

import { readdirSync } from 'fs';
const bad = readdirSync('reports')
  .map(name => ({ name, m: name.match(/^(\d+)-/) }))
  .filter(({ m }) => m && parseInt(m[1], 10) > 1e9);
if (bad.length) throw new Error(`reports/ has non-report huge-number files: ${bad.map(b => b.name)}`);

Try / catch

try {
  ids = await reserveReportNumbers(count);
} catch (err) {
  if (err instanceof RangeError && err.message.includes('No safe report-number range')) {
    // inspect reports/ and tracker for huge digit-prefixed filenames/rows
  }
  throw err;
}

Prevention

When it happens

Trigger: A reports/ directory or tracker containing a filename whose leading digits parse to a number near Number.MAX_SAFE_INTEGER (e.g. a timestamped filename like 1789569700000-foo.md matching /^(\d+)-/), making highestNumber(occupied) enormous; the very first iteration then fails the isSafeInteger check.

Common situations: Non-report files dropped into reports/ whose names start with epoch-millisecond timestamps or large IDs. A misconfigured CAREER_OPS_REPORTS_DIR pointing at a directory of timestamp-named logs. Corrupted tracker rows with huge num or report-link numbers.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at reserve-report-num.mjs:195

  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();

    for (let tries = 0; tries < MAX_RETRIES; tries++) {
      const end = base + (count - 1);
      if (!Number.isSafeInteger(base) || !Number.isSafeInteger(end)) {
        throw new RangeError('No safe report-number range remains');
      }
      const claimed = [];
      let failedAt = null;
      for (let num = base; num <= end; num++) {
        if (claimSlot(reportsDir, num, occupied, token)) {
          claimed.push(num);
        } else {
          failedAt = num;
          break;
        }
      }
      if (failedAt == null) {
        Object.defineProperty(claimed, RESERVATION_TOKEN, { value: token });
        return claimed;
      }

      for (const num of claimed) releaseSlot(reportsDir, num, { token });
      occupied = collectOccupied(reportsDir, trackerPath);

View on GitHub (pinned to aac998c7ed)