santifer/career-ops · error · TypeError

Report number must be a positive integer, got ${num}

Error message

Report number must be a positive integer, got ${num}

What it means

formatReportNumber() is a pure formatting helper that zero-pads a report ID to three digits (e.g. 5 -> '005'). It throws a TypeError when its argument is not a safe positive integer — specifically when Number.isSafeInteger(num) is false or num < 1. This guards the sentinel filename construction (`${formatReportNumber(num)}-RESERVED.md`) from producing malformed paths.

Source

Thrown at reserve-report-num.mjs:42

import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';
import {
  extractTrackerReportNumbers, parseTrackerRow, resolveColumns,
} from './tracker-parse.mjs';
import {
  acquireTrackerLock, canonicalizeTrackerPath, resolveTrackerPath, trackerLockDirFor,
} from './tracker-utils.mjs';

const ROOT = dirname(fileURLToPath(import.meta.url));
const MAX_SENTINEL_AGE_MS = 4 * 60 * 60 * 1000;
const MAX_RETRIES = 50;
const MAX_COUNT = 50;
const RESERVATION_TOKEN = Symbol('career-ops-report-reservation-token');

/** Format a report ID with a minimum width of three digits. */
export function formatReportNumber(num) {
  if (!Number.isSafeInteger(num) || num < 1) {
    throw new TypeError(`Report number must be a positive integer, got ${num}`);
  }
  return String(num).padStart(3, '0');
}

function reportsDirFor(options = {}) {
  return resolve(options.reportsDir
    || process.env.CAREER_OPS_REPORTS_DIR
    || join(options.rootDir || ROOT, 'reports'));
}

function trackerPathFor(options = {}) {
  return options.trackerPath
    ? canonicalizeTrackerPath(options.trackerPath)
    : resolveTrackerPath(options.rootDir || ROOT);
}

function occupiedFromReports(reportsDir) {
  const occupied = new Set();

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Coerce and validate before calling: const n = Number(input); if (!Number.isSafeInteger(n) || n < 1) throw ...; formatReportNumber(n)
  2. If reading from CLI argv, wrap with Number(arg) and validate in your argument parser (e.g. parseArgs with a custom validator).
  3. If the value comes from a filename, extract only the leading digit run with /^\(\d+)/ and parse it.
  4. Ensure loops that compute report numbers start at 1, not 0.

Example fix

// before
const num = process.argv[2]; // string '5'
formatReportNumber(num); // throws TypeError

// after
const num = Number(process.argv[2]);
if (!Number.isSafeInteger(num) || num < 1) {
  throw new TypeError(`Expected positive integer report number, got ${process.argv[2]}`);
}
formatReportNumber(num);
Defensive patterns

Strategy: validation

Validate before calling

function validateReportNumber(num) {
  const n = Number(num);
  if (!Number.isSafeInteger(n) || n < 1) {
    throw new TypeError(`Report number must be a positive integer, got ${num}`);
  }
  return n;
}
// call before formatReportNumber:
formatReportNumber(validateReportNumber(input));

Type guard

function isReportNumber(value) {
  return Number.isSafeInteger(value) && value >= 1;
}

Try / catch

try {
  const formatted = formatReportNumber(num);
} catch (err) {
  if (err instanceof TypeError && err.message.includes('positive integer')) {
    console.error(`Invalid report number input: ${num}`);
    process.exit(2);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling formatReportNumber(0), formatReportNumber(-1), formatReportNumber(3.5), formatReportNumber(NaN), formatReportNumber('5'), or formatReportNumber(Number.MAX_SAFE_INTEGER + 1). Also triggered indirectly by reserveReportNumbers/releaseReportNumbers when a caller-supplied number in the array fails the same check.

Common situations: Passing a string from CLI argv without Number() coercion; off-by-one math producing 0; parsing a report number from a filename regex that captured a non-numeric group; loading a number from JSON config where it was stored as a string.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/af4554d2a18c7c3e. Report an issue: GitHub.