santifer/career-ops · error · Error

invalid reportNum "${entryReport}" (use the numeric report n

Error message

invalid reportNum "${entryReport}" (use the numeric report number)

What it means

The optional per-entry reportNum must be all digits after trimming (/^\d+$/). It exists to tag rendered PDFs with their tracker report number, so decorated forms are rejected: 'R-064', 'report 64', '#64' all fail; '064' and '64' pass. Empty string / omitted is fine.

Source

Thrown at generate-pdf.mjs:1271

  // single render is: it is the anchor readStyleTokens() and the cv.md read
  // already use, so one profile.yml supplies every setting.
  const cvSectionOrder = readCvSectionOrder(resolve(workspaceRoot, 'config', 'profile.yml'));

  for (let i = 0; i < manifest.length; i++) {
    const spec = manifest[i];
    try {
      if (!spec || typeof spec.input !== 'string' || typeof spec.output !== 'string') {
        throw new Error('each entry needs a string "input" and "output"');
      }

      const entryFormat = (spec.format || globals.format).toLowerCase();
      if (!validFormats.includes(entryFormat)) {
        throw new Error(`invalid format "${entryFormat}" (use: ${validFormats.join(', ')})`);
      }

      const entryReport = (spec.reportNum ?? '').toString().trim();
      if (entryReport && !/^\d+$/.test(entryReport)) {
        throw new Error(`invalid reportNum "${entryReport}" (use the numeric report number)`);
      }

      // Resolve manifest-supplied input/output relative to the manifest's own
      // directory, not process.cwd(), so a manifest renders identically wherever
      // the batch is launched from. Absolute paths in the manifest still win
      // (resolve() ignores the base when the tail is absolute).
      const entryInput = resolve(manifestDir, spec.input);
      const entryOutput = resolve(manifestDir, spec.output);

      // Path-containment guards (realpath-based): keep the read and write inside
      // the tracker workspace even through a symlinked ancestor. A batch
      // manifest that escapes the workspace is malformed/tampered and is
      // recorded as a per-entry failure rather than read or written.
      assertInsideWorkspace(entryInput, 'input');
      if (!isWorkspaceOutputPath(entryOutput, workspaceRoot)) {
        throw new Error(`output escapes the tracker workspace: ${entryOutput}`);
      }

View on GitHub (pinned to 60398d6549)

Solutions

  1. Use the bare numeric report number: "reportNum": "064".
  2. Strip decorations in the generator: `String(ref).replace(/[^0-9]/g, '')` before writing the manifest.
  3. If the entry has no associated report, omit reportNum entirely rather than writing a placeholder.

Example fix

// before
{ "input": "x.html", "output": "x.pdf", "reportNum": "R-064" }

// after
{ "input": "x.html", "output": "x.pdf", "reportNum": "064" }
Defensive patterns

Strategy: validation

Validate before calling

const bad = manifest
  .filter(e => e.reportNum !== undefined && e.reportNum !== '' && !/^\d+$/.test(String(e.reportNum).trim()))
  .map(e => JSON.stringify(e.reportNum));
if (bad.length) { console.error(`reportNum must be bare digits, got: ${bad.join(', ')}`); process.exit(1); }

Type guard

/** @param {unknown} v */
function isValidReportNum(v) {
  if (v === undefined || v === null || v === '') return true; // optional
  return /^\d+$/.test(String(v).trim());
}

Prevention

When it happens

Trigger: A manifest generator writes report references as 'report-064' or '#064' from UI-facing strings; someone pastes the report column including its markdown link syntax; negative or zero-prefixed-with-letter ids from an external tracker export.

Common situations: Glue code between an ATS/dashboard and the batch renderer that formats ids for display; manifests assembled by hand from tracker notes.

Related errors


AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20). Data as JSON: /api/errors/fd3a6645377af067. Report an issue: GitHub.