santifer/career-ops · warning

Orphan report — no tracker row references #

Error message

Orphan report — no tracker row references #${num}: reports/${name}

What it means

A warning from verify-pipeline.mjs (Check 10, #1425) that a report file in reports/ has a report number no tracker row references — an 'orphan report'. Every evaluation report should be linked from a data/applications.md row (Report column: `[NNN](reports/...)`); orphans mean an evaluation happened but was never tracked, so pipeline stats and dedup miss it.

Solutions

  1. Evaluate whether the report is still relevant: if yes, add its tracker row via the TSV-inbox (batch/tracker-additions/) and `node merge-tracker.mjs`.
  2. If the report is obsolete, delete it or move it out of reports/ so numbering and checks stay clean.
  3. Re-link a lost reference by restoring the `[NNN](reports/NNN-...)` markdown in the existing row's Report column.
  4. Run `node merge-tracker.mjs` after every evaluation batch to prevent orphans from ever accumulating.

Example fix

// before: row exists but Report cell empty
| 42 | 2026-09-16 | Acme | Sr Backend | 4.5/5 | Evaluated | ❌ |  | ... |
// after
| 42 | 2026-09-16 | Acme | Sr Backend | 4.5/5 | Evaluated | ❌ | [42](reports/042-acme-sr-backend-2026-09-16.md) | ... |
Defensive patterns

Strategy: validation

Validate before calling

const nums = new Set(reportFiles.map(n => parseInt(n.match(/^(\d{3})-/)[1], 10)));
const referenced = new Set(rows.flatMap(r => r.reportNums));
for (const n of nums) if (!referenced.has(n)) console.warn(`orphan report #${n} — add or delete`);

Prevention

When it happens

Trigger: Running `node verify-pipeline.mjs` when reports/ contains a numbered report whose number is absent from every tracker row's Report link — e.g. the tracker row was deleted, the report was written but merge-tracker.mjs never ran/failed, or the row's report link was edited away.

Common situations: Deleting tracker rows during manual cleanup while keeping reports/; interrupted batches (report written, TSV merge failed); backfilled tracker entries that forgot the report link; renaming report numbers by hand.

Related errors


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

Appendix: source

Thrown at verify-pipeline.mjs:361

for (const e of entries) {
  const linkTexts = [...e.report.matchAll(/\[(\d+)\]/g)];
  const linkTargets = [...e.report.matchAll(/\]\(([^)]+)\)/g)];
  if (linkTexts.length === 0 && linkTargets.length === 0) {
    referencedNums.add(e.num);
    continue;
  }
  for (const lt of linkTexts) referencedNums.add(parseInt(lt[1], 10));
  for (const lt of linkTargets) {
    const m = lt[1].split(/[\\/]/).pop().match(/^(\d+)-/);
    if (m) referencedNums.add(parseInt(m[1], 10));
  }
}

let orphanReports = 0;
for (const name of reportFiles) {
  const num = parseInt(name.match(REPORT_FILE_RE)[1], 10);
  if (!referencedNums.has(num)) {
    warn(`Orphan report — no tracker row references #${num}: reports/${name}`);
    orphanReports++;
  }
}
if (orphanReports === 0) ok('No orphan reports');

// --- Check 11: Via channel consistency (#1596) ---
// The Via column records the intermediary (agency/recruiter firm; `—` when the
// application was direct). Unknown employers use the structural marker `?` in
// Company — never a word like "Confidential", which is locale-dependent and can
// collide with a real firm name.
let viaIssues = 0;
const CONFIDENTIAL_WORD_RE = /^(confidential|vertraulich|confidentiel|confidencial|riservato|gizli|機密|سري)$/i;
for (const e of entries) {
  const company = String(e.company || '').trim();
  const via = String(e.via || '').trim();
  if (company === '?') {
    if (COLMAP.via == null) {
      warn(`#${e.num}: unknown employer (?) but the tracker has no Via column — add it with: node merge-tracker.mjs --migrate-via`);

View on GitHub (pinned to e7abd431fc)