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
- Coerce and validate before calling: const n = Number(input); if (!Number.isSafeInteger(n) || n < 1) throw ...; formatReportNumber(n)
- If reading from CLI argv, wrap with Number(arg) and validate in your argument parser (e.g. parseArgs with a custom validator).
- If the value comes from a filename, extract only the leading digit run with /^\(\d+)/ and parse it.
- 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
- Always coerce CLI argv with Number() before passing to report-number functions.
- Validate at the I/O boundary (CLI parser, config loader), not at the leaf formatting function.
- When parsing report numbers from filenames, capture only the leading digit run.
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
- Reservation count must be an integer from 1 to ${MAX_COUNT}
- payload must include at least one of: cv, articleDigest
- payload.cv requires { section, entry }
- payload.cv requires a non-empty dedupKey (used for dedup/ide
- payload.articleDigest requires { entry }
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/af4554d2a18c7c3e.
Report an issue: GitHub.