{"record":{"id":"af4554d2a18c7c3e","repo":"santifer/career-ops","slug":"report-number-must-be-a-positive-integer-got-num","errorCode":null,"errorMessage":"Report number must be a positive integer, got ${num}","messagePattern":"Report number must be a positive integer, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"reserve-report-num.mjs","lineNumber":44,"sourceCode":"import { getCareerOpsRoot } from './path-resolver.mjs';\nimport {\n  extractTrackerReportNumbers, parseTrackerRow, resolveColumns,\n} from './tracker-parse.mjs';\nimport {\n  acquireTrackerLock, canonicalizeTrackerPath, resolveTrackerPath, trackerLockDirFor,\n} from './tracker-utils.mjs';\nimport { isMainModule } from './lib/is-main-module.mjs';\n\nconst ROOT = getCareerOpsRoot();\nconst MAX_SENTINEL_AGE_MS = 4 * 60 * 60 * 1000;\nconst MAX_RETRIES = 50;\nconst MAX_COUNT = 50;\nconst RESERVATION_TOKEN = Symbol('career-ops-report-reservation-token');\n\n/** Format a report ID with a minimum width of three digits. */\nexport function formatReportNumber(num) {\n  if (!Number.isSafeInteger(num) || num < 1) {\n    throw new TypeError(`Report number must be a positive integer, got ${num}`);\n  }\n  return String(num).padStart(3, '0');\n}\n\nfunction reportsDirFor(options = {}) {\n  return resolve(options.reportsDir\n    || process.env.CAREER_OPS_REPORTS_DIR\n    || join(options.rootDir || ROOT, 'reports'));\n}\n\nfunction trackerPathFor(options = {}) {\n  return options.trackerPath\n    ? canonicalizeTrackerPath(options.trackerPath)\n    : resolveTrackerPath(options.rootDir || ROOT);\n}\n\n// A bare date file is not a report. `scan-ats-full.mjs --md-out reports/` writes\n// its digest as `reports/YYYY-MM-DD.md`, which matches `/^(\\d+)-/` and is read","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/reserve-report-num.mjs#L26-L62","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nformatReportNumber(process.argv[2]); // \"42\" → TypeError\n// after\nconst n = Number.parseInt(process.argv[2], 10);\nif (Number.isSafeInteger(n) && n >= 1) formatReportNumber(n);","handlingStrategy":"validation","validationCode":"// validate before formatting\nfunction toReportNumber(input) {\n  const n = typeof input === 'number' ? input : Number.parseInt(String(input ?? ''), 10);\n  if (!Number.isSafeInteger(n) || n < 1) {\n    throw new TypeError(`Report number must be a positive integer, got ${input}`);\n  }\n  return n;\n}","typeGuard":"function isValidReportNumber(v) {\n  return Number.isSafeInteger(v) && v >= 1;\n}","tryCatchPattern":"try {\n  const id = formatReportNumber(num);\n} catch (e) {\n  if (e instanceof TypeError && e.message.startsWith('Report number must be a positive integer')) {\n    console.warn(`Bad report number ${num}: parse CLI args / tracker rows into integers first.`);\n  } else throw e;\n}","preventionTips":["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"],"tags":["validation","report-numbering","argument","typeerror"],"backgroundTag":"invalid-argument-value","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}