santifer/career-ops · error · SeedError

USAGE

USAGE

Error message

Invalid appNum: ${appNum}

What it means

seedFollowup requires the application tracker number to be a positive integer. Before doing any I/O it validates appNum and throws a SeedError with code USAGE if it is not an integer greater than 0. This is a caller-contract error: the wrong type or value was passed for the tracker row id.

Solutions

  1. Pass the tracker row number as a positive integer: seedFollowup(42).
  2. Convert string input explicitly: seedFollowup(Number.parseInt(raw, 10)).
  3. Guard against 0/negative/NaN values before calling.
  4. Check the tracker table to confirm the actual row # before seeding.

Example fix

// before
await seedFollowup(process.argv[2]);
// after
const appNum = Number.parseInt(process.argv[2], 10);
if (!Number.isInteger(appNum) || appNum <= 0) {
  console.error('Usage: seed <appNum> where appNum is a positive integer');
  process.exit(1);
}
await seedFollowup(appNum);
Defensive patterns

Strategy: validation

Validate before calling

function isValidAppNum(n) {
  return Number.isInteger(n) && n > 0;
}
if (!isValidAppNum(appNum)) throw new Error(`appNum must be a positive integer, got ${appNum}`);

Type guard

const isAppNum = (v) => typeof v === 'number' && Number.isInteger(v) && v > 0;

Try / catch

try {
  await seedFollowup(appNum);
} catch (e) {
  if (e.code === 'USAGE') {
    console.error(`Bad appNum: ${e.message}. Pass the integer tracker row #.`);
    process.exitCode = 2;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling seedFollowup('42') (string instead of number), seedFollowup(0), seedFollowup(-3), seedFollowup(NaN), or a float like 4.5; passing a value parsed from CLI args without Number.parseInt conversion.

Common situations: Script glue that passes process.argv entries directly (always strings); off-by-one generation of row numbers starting at 0; JSON payloads where the id arrived as a string from an API or TSV cell.

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


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/18b58a3d2d6f84d4. Report an issue: GitHub.

Appendix: source

Thrown at followup-seed.mjs:545

 */
export async function withFollowupsLock(followupsPath, fn, options = {}) {
  const resolvedPath = resolveFollowupsPath(followupsPath);
  const lockDir = resolveLockDir(options.lockDir, resolvedPath);
  const lock = await acquireFollowupsLock(lockDir, resolvedPath, {
    timeoutMs: options.timeoutMs ?? envInt('CAREER_OPS_FOLLOWUPS_LOCK_TIMEOUT_MS', 60_000),
    retryMs: options.retryMs ?? envInt('CAREER_OPS_FOLLOWUPS_LOCK_RETRY_MS', 75),
    staleMs: options.staleMs ?? envInt('CAREER_OPS_FOLLOWUPS_LOCK_STALE_MS', 10 * 60_000),
  });
  try {
    return await fn();
  } finally {
    lock.release();
  }
}

export async function seedFollowup(appNum, options = {}) {
  if (!Number.isInteger(appNum) || appNum <= 0) {
    throw new SeedError('USAGE', `Invalid appNum: ${appNum}`);
  }
  if (options.date != null && !isValidCalendarDate(options.date)) {
    throw new SeedError('INVALID_DATE', `--date must be a real calendar date in YYYY-MM-DD form: ${options.date}`);
  }

  const trackerPath = resolveTrackerPath(options.trackerPath);
  const followupsPath = resolveFollowupsPath(options.followupsPath);

  if (!existsSync(trackerPath)) {
    throw new SeedError('ROW_NOT_FOUND', `Tracker not found at ${trackerPath}`);
  }
  const rows = readTrackerRows(trackerPath);
  const row = rows.find(r => r.num === appNum);
  if (!row) {
    throw new SeedError('ROW_NOT_FOUND', `Application #${appNum} not found in ${trackerPath}`);
  }

  const normalized = normalizeStatus(row.status);

View on GitHub (pinned to aac998c7ed)