santifer/career-ops · error · TypeError
Report number must be a positive integer, got
Error message
Report number must be a positive integer, got ${num} What it means
formatReportNumber converts a report ID into its canonical 3-digit zero-padded form (e.g. 42 → '042') and validates the input first: it must be a safe positive integer. This TypeError is thrown when the value passed is 0, negative, fractional, NaN, non-numeric, or a numeric string, protecting the report-file numbering scheme from producing unsortable or colliding filenames.
Solutions
- Coerce with Number() and validate before calling: only pass Number.isSafeInteger(x) && x >= 1 values
- Check the upstream source of the number — CLI args must be parsed (Number.parseInt(arg, 10)) before formatting
- If computing the next report number, ensure the max-extraction handles an empty tracker by starting at 1
- Reject the operation upstream with a clear message instead of letting 0/NaN reach the formatter
Example fix
// before formatReportNumber(process.argv[2]); // "42" → TypeError // after const n = Number.parseInt(process.argv[2], 10); if (Number.isSafeInteger(n) && n >= 1) formatReportNumber(n);
Defensive patterns
Strategy: validation
Validate before calling
// validate before formatting
function toReportNumber(input) {
const n = typeof input === 'number' ? input : Number.parseInt(String(input ?? ''), 10);
if (!Number.isSafeInteger(n) || n < 1) {
throw new TypeError(`Report number must be a positive integer, got ${input}`);
}
return n;
} Type guard
function isValidReportNumber(v) {
return Number.isSafeInteger(v) && v >= 1;
} Try / catch
try {
const id = formatReportNumber(num);
} catch (e) {
if (e instanceof TypeError && e.message.startsWith('Report number must be a positive integer')) {
console.warn(`Bad report number ${num}: parse CLI args / tracker rows into integers first.`);
} else throw e;
} Prevention
- Always Number.parseInt CLI arguments and tracker cells before formatting
- Handle the empty-tracker case explicitly (start numbering at 1, never 0)
- Treat 'N/A'/'—' score sentinels as non-numeric and exclude them from numbering math
- Write a unit test covering 0, NaN, negative, fractional, and string inputs
When it happens
Trigger: Calling formatReportNumber with a string parsed from user input ('42' not Number('42')), a value of 0 when no reports exist yet, a NaN from parseInt on a malformed argument, or a float from a division/average computed upstream.
Common situations: CLI argument parsing that forgot Number(); tracker rows using 'N/A' sentinels passed to the formatter; computing max+1 over an empty set yielding NaN or 1 misread as 0; tests passing strings for convenience.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- apify: invalid timeoutMs
- createLockWaitPolicy: hardDeadline must be a number or…
- 4dayweek: invalid URL
- 4dayweek: URL must use HTTPS
- a 40-hex commit --sha is required
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/af4554d2a18c7c3e.
Report an issue: GitHub.
Appendix: source
Thrown at reserve-report-num.mjs:44
import { getCareerOpsRoot } from './path-resolver.mjs';
import {
extractTrackerReportNumbers, parseTrackerRow, resolveColumns,
} from './tracker-parse.mjs';
import {
acquireTrackerLock, canonicalizeTrackerPath, resolveTrackerPath, trackerLockDirFor,
} from './tracker-utils.mjs';
import { isMainModule } from './lib/is-main-module.mjs';
const ROOT = getCareerOpsRoot();
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);
}
// A bare date file is not a report. `scan-ats-full.mjs --md-out reports/` writes
// its digest as `reports/YYYY-MM-DD.md`, which matches `/^(\d+)-/` and is readView on GitHub (pinned to aac998c7ed)