{"record":{"id":"24db861e84833be0","repo":"santifer/career-ops","slug":"tracker-addition-num-already-used-assigni","errorCode":null,"errorMessage":"⚠️  Tracker #${addition.num} already used; assigning #${entryNum} to ${addition.company} — ${addition.role}. Report link remains ${addition.report}.","messagePattern":"⚠️  Tracker #(.+?) already used; assigning #(.+?) to (.+?) — (.+?)\\. Report link remains (.+?)\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":1265,"sourceCode":"      console.error(\n        `❌ ${file}: could not locate tracker row #${duplicate.num} ` +\n        `(${duplicate.company} — ${duplicate.role}) to update; this evaluation was NOT merged.`,\n      );\n      failedAdditions.push(file);\n    }\n  } else {\n    // New entry - preserve the TSV's reserved ID whenever it is actually\n    // free. Parallel workers can finish out of order, so a valid reservation\n    // may be lower than the current tracker maximum (#1733). Renumber only on\n    // a real collision, using the next free ID above the current maximum and\n    // warning loudly so report/tracker drift is visible (#1704).\n    let entryNum;\n    if (!usedNumbers.has(addition.num)) {\n      entryNum = addition.num;\n    } else {\n      entryNum = maxNum + 1;\n      while (usedNumbers.has(entryNum)) entryNum++;\n      console.warn(\n        `⚠️  Tracker #${addition.num} already used; assigning #${entryNum} to ` +\n        `${addition.company} — ${addition.role}. Report link remains ${addition.report}.`,\n      );\n    }\n    usedNumbers.add(entryNum);\n    if (entryNum > maxNum) maxNum = entryNum;\n\n    const pdf = reportNum && pdfIndex.has(String(reportNum)) ? '✅' : addition.pdf;\n    const newLine = buildRow({\n      num: entryNum, date: addition.date, company: addition.company, role: addition.role,\n      via: addition.via || '—',\n      location: addition.location || '—',\n      score: addition.score, status: addition.status, pdf,\n      report: addition.report, notes: addition.notes,\n      // Write the key on the way in. Backfill is the one-time EXPAND phase for\n      // rows that predate the column; a row added today must carry its own URL\n      // or Pass 0 can never match it and dedup stays fuzzy-only for new work.\n      url: addition.url || '',","sourceCodeStart":1247,"sourceCodeEnd":1283,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/merge-tracker.mjs#L1247-L1283","documentation":"merge-tracker.mjs consumes per-evaluation TSVs from batch/tracker-additions/, each carrying a reserved entry number. When that number is already occupied in applications.md (usedNumbers), the merge refuses to reuse it, assigns the next free ID above the current maximum, and warns loudly because the report file still carries the old number -- a deliberate report/tracker drift signal (#1704, #1733). Out-of-order finishes from parallel workers are the expected cause; the renumber only happens on a real collision.","triggerScenarios":"Parallel batch evaluators whose reservations expired or were reused before merge; two TSVs in tracker-additions/ claiming the same num column; merging TSVs from an older session after the tracker advanced past their numbers; hand-written TSVs with a guessed num.","commonSituations":"Fan-out workers reserving via reserve-report-num.mjs more than 4h before merging (sentinels GC'd, slots freed and re-claimed); copy-pasting a previous TSV as a template and forgetting to bump the number; merging a stale batch directory alongside a fresh one.","solutions":["Run node reserve-report-num.mjs --count N immediately before spawning workers and give each worker its own slot; merge soon after so sentinels are still alive.","If the warning already fired, trust the renumbered tracker row and fix the stale reference: correct the report link or note on the row, or rename the report file to match the new number if nothing else references it.","Inspect batch/tracker-additions/ for duplicate num columns and correct the stale TSV before re-running node merge-tracker.mjs.","Release ranges you did not use with node reserve-report-num.mjs --release NNN-MMM so numbers return to the pool."],"exampleFix":"# before: two TSVs both claim #064 (second one triggers the warning)\n064  2026-08-20  Acme  Senior ML Engineer  Evaluated  4.2/5  ...  [064](reports/064-acme-2026-08-20.md)  ...\n# after: reserve first, then write each TSV with its own slot\n$ node reserve-report-num.mjs --count 2   # -> 064-065\n064  2026-08-20  Acme   Senior ML Engineer  Evaluated  4.2/5  ...\n065  2026-08-20  Globex  Data Platform Lead  Evaluated  4.0/5  ...","handlingStrategy":"validation","validationCode":"// Before merging, check every TSV's num against the tracker's used numbers\nimport { readFileSync, readdirSync } from 'node:fs';\nconst used = new Set(\n  [...readFileSync('data/applications.md', 'utf-8').matchAll(/^\\|\\s*(\\d+)\\s*\\|/gm)].map(m => Number(m[1]))\n);\nfor (const f of readdirSync('batch/tracker-additions').filter(f => f.endsWith('.tsv'))) {\n  const num = Number(readFileSync(`batch/tracker-additions/${f}`, 'utf-8').split('\\t')[0]);\n  if (used.has(num)) console.error(`${f}: num #${num} already used — reserve a free slot first`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always node reserve-report-num.mjs --count N immediately before spawning workers and merge promptly so sentinels (4h lifetime) are still valid.","Release unused ranges with --release so numbers return to the pool instead of forcing later renumbering.","Never hand-write the num column in TSVs; take it from a reservation.","Treat this warning as report/tracker drift: after every parallel batch, confirm each report file's number matches its tracker row."],"tags":["merge-tracker","id-collision","parallel-workers","report-numbering","tracker-drift"],"backgroundTag":"duplicate-id-collision","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}