santifer/career-ops · error · Error

reportNum must be a numeric report number

Error message

reportNum must be a numeric report number

What it means

applicationArtifactPaths builds a stable directory/file bundle key of the form NNN-company-role and requires reportNum to be a string of digits only (checked with /^\d+$/). If reportNum is absent, non-numeric, or contains separators like '#' or leading text, it throws. This guarantees predictable, sortable artifact paths under the output root.

Solutions

  1. Pass the plain numeric report number: applicationArtifactPaths({ reportNum: 42, company, role }).
  2. Normalize extracted values first, e.g. String(reportNum).replace(/\D/g, ''), and confirm it is non-empty before calling.
  3. Check upstream parsing that produced reportNum — log it and fix the extraction from the tracker/report filename.

Example fix

// before
const paths = applicationArtifactPaths({ reportNum: row.num, company, role }); // row.num = '#42'
// after
const num = String(row.num).replace(/^#/, '');
if (!/^\d+$/.test(num)) throw new Error(`bad report number: ${row.num}`);
const paths = applicationArtifactPaths({ reportNum: num, company, role });
Defensive patterns

Strategy: type-guard

Validate before calling

function isNumericReportNum(n) { return /^\d+$/.test(String(n ?? '')); }
if (!isNumericReportNum(reportNum)) throw new Error(`reportNum must be numeric, got ${reportNum}`);

Type guard

function isReportNum(v) {
  return (typeof v === 'number' && Number.isInteger(v) && v > 0) || (typeof v === 'string' && /^\d+$/.test(v));
}

Try / catch

try {
  const paths = applicationArtifactPaths({ reportNum, company, role });
} catch (err) {
  if (err.message === 'reportNum must be a numeric report number') {
    console.error(`Strip decorations from report number: got ${reportNum}`);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling applicationArtifactPaths with reportNum: undefined, with a string like '042-a' or '#42', with a number in a form String() renders non-numerically (e.g. NaN), or passing a tracker row label instead of the raw report number.

Common situations: Passing values parsed from markdown links like '[042](reports/...)' without stripping the decoration; using tracker row numbers that include prefixes; a variable that failed to populate upstream so undefined flows in.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at application-artifacts.mjs:33

import { isMainModule } from './lib/is-main-module.mjs';

const DEFAULT_OUTPUT_ROOT = resolve('output');
const DECISIONS = new Set(['reuse', 'reuse-with-edits', 'regenerate']);

/** Convert a user-facing label into a safe, readable path segment. */
export function slugifySegment(value, fallback = 'application') {
  const slug = String(value ?? '')
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
  return slug || fallback;
}

/** Return all stable paths belonging to one application artifact bundle. */
export function applicationArtifactPaths({ reportNum, company, role, version = 1, root = DEFAULT_OUTPUT_ROOT }) {
  if (!/^\d+$/.test(String(reportNum ?? ''))) {
    throw new Error('reportNum must be a numeric report number');
  }
  if (!/^\d+$/.test(String(version ?? '')) || Number(version) < 1) {
    throw new Error('version must be a positive integer');
  }
  const key = `${String(reportNum).padStart(3, '0')}-${slugifySegment(company)}-${slugifySegment(role, 'role')}`;
  const applicationRoot = join(resolve(root), key);
  const tailoredRoot = join(applicationRoot, 'cv', 'tailored', `v${String(version).padStart(3, '0')}`);
  return {
    key,
    root: applicationRoot,
    jd: {
      current: join(applicationRoot, 'jd', 'current.md'),
      previous: join(applicationRoot, 'jd', 'previous.md'),
    },
    cv: {
      source: {
        html: join(applicationRoot, 'cv', 'source', 'original.html'),
        pdf: join(applicationRoot, 'cv', 'source', 'original.pdf'),

View on GitHub (pinned to aac998c7ed)