{"record":{"id":"26a4ed6fd9adbb92","repo":"santifer/career-ops","slug":"reservation-count-must-be-an-integer-from-1-to-max-count","errorCode":null,"errorMessage":"Reservation count must be an integer from 1 to ${MAX_COUNT}","messagePattern":"Reservation count must be an integer from 1 to (.+?)","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"reserve-report-num.mjs","lineNumber":172,"sourceCode":"  if (!Number.isSafeInteger(pid) || pid <= 0) return false;\n  try {\n    process.kill(pid, 0);\n    return true;\n  } catch (err) {\n    return err?.code === 'EPERM';\n  }\n}\n\n/**\n * Reserve one or more contiguous report IDs.\n *\n * @param {number} [count=1] Number of IDs to reserve (1-50).\n * @param {object} [options] Path and lock overrides.\n * @returns {Promise<number[]>} Reserved numeric IDs.\n */\nexport async function reserveReportNumbers(count = 1, options = {}) {\n  if (!Number.isInteger(count) || count < 1 || count > MAX_COUNT) {\n    throw new RangeError(`Reservation count must be an integer from 1 to ${MAX_COUNT}`);\n  }\n\n  const reportsDir = reportsDirFor(options);\n  const trackerPath = trackerPathFor(options);\n  mkdirSync(reportsDir, { recursive: true });\n\n  const lock = await acquireTrackerLock(trackerLockDirFor(trackerPath), {\n    timeoutMs: Number(process.env.CAREER_OPS_TRACKER_LOCK_TIMEOUT_MS) || 60_000,\n    retryMs: Number(process.env.CAREER_OPS_TRACKER_LOCK_RETRY_MS) || 75,\n    staleMs: Number(process.env.CAREER_OPS_TRACKER_LOCK_STALE_MS) || 10 * 60_000,\n    tracker: trackerPath,\n    ...options.lockOptions,\n  });\n\n  try {\n    let occupied = collectOccupied(reportsDir, trackerPath);\n    let base = highestNumber(occupied) + 1;\n    const token = randomUUID();","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/reserve-report-num.mjs#L154-L190","documentation":"reserveReportNumbers(count) validates its count argument before doing any work: it must be an integer between 1 and MAX_COUNT (50). A RangeError is thrown immediately when count is a non-integer (e.g. a float or a numeric string coming from CLI parsing), zero, negative, or greater than 50. This is an upfront argument guard so the allocator never attempts an impossible reservation.","triggerScenarios":"Calling reserveReportNumbers(0), reserveReportNumbers(-3), reserveReportNumbers(51), reserveReportNumbers(2.5), or passing a string parsed from --count without Number conversion (Number.isInteger('8') is false). Also happens when a config value like a batch-size env var is NaN or undefined and defaults are bypassed.","commonSituations":"CLI flag mishandling: `--count` read as a raw string or parseFloat result like 8.5. Config mistakes: an env var CAREER_OPS_BATCH_SIZE set to '0' or '100'. Off-by-one math: computing count as numbers.length when the array is empty (0). Automation scripts fanning out more than the documented 50-worker cap.","solutions":["Clamp and validate count before calling: const n = Math.floor(Number(raw)); if (Number.isInteger(n) && n >= 1 && n <= 50) await reserveReportNumbers(n);","When you need more than 50 IDs, make multiple sequential reserveReportNumbers calls (e.g. two calls of 40) instead of one oversized call.","Convert CLI/env strings with Number() or parseInt(raw, 10) and check Number.isInteger before passing them.","Default to the documented count=1 when the input is not a usable integer."],"exampleFix":"// before\nconst count = process.env.FANOUT;\nconst ids = await reserveReportNumbers(count); // throws: '8' is not an integer\n// after\nconst count = Number.parseInt(process.env.FANOUT ?? '1', 10);\nif (!Number.isInteger(count) || count < 1 || count > 50) {\n  throw new Error(`FANOUT must be an integer 1-50, got ${process.env.FANOUT}`);\n}\nconst ids = await reserveReportNumbers(count);","handlingStrategy":"validation","validationCode":"function toCount(raw) {\n  const n = Number.parseInt(raw, 10);\n  if (!Number.isInteger(n) || n < 1 || n > 50) {\n    throw new RangeError(`count must be an integer 1-50, got ${raw}`);\n  }\n  return n;\n}\nconst ids = await reserveReportNumbers(toCount(userInput));","typeGuard":"const isValidCount = (v) => Number.isInteger(v) && v >= 1 && v <= 50;","tryCatchPattern":"let ids;\ntry {\n  ids = await reserveReportNumbers(count);\n} catch (err) {\n  if (err instanceof RangeError && /Reservation count/.test(err.message)) {\n    ids = await reserveReportNumbers(1); // safe default\n  } else throw err;\n}","preventionTips":["Always parse CLI/env values with Number.parseInt(x, 10) before passing them as count.","Validate with Number.isInteger — numeric strings and NaN both fail it.","Cap fan-out at 50 workers; split larger batches into multiple reservations.","Default to 1 (the documented parameter default) when input is missing."],"tags":["argument-validation","range-error","input-validation"],"backgroundTag":"argument-out-of-range","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"}