santifer/career-ops · warning
⚠️ Tracker #${addition.num} already used; assigning #${entr
Error message
⚠️ Tracker #${addition.num} already used; assigning #${entryNum} to ${addition.company} — ${addition.role}. Report link remains ${addition.report}. What it means
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.
Source
Thrown at merge-tracker.mjs:1265
console.error(
`❌ ${file}: could not locate tracker row #${duplicate.num} ` +
`(${duplicate.company} — ${duplicate.role}) to update; this evaluation was NOT merged.`,
);
failedAdditions.push(file);
}
} else {
// New entry - preserve the TSV's reserved ID whenever it is actually
// free. Parallel workers can finish out of order, so a valid reservation
// may be lower than the current tracker maximum (#1733). Renumber only on
// a real collision, using the next free ID above the current maximum and
// warning loudly so report/tracker drift is visible (#1704).
let entryNum;
if (!usedNumbers.has(addition.num)) {
entryNum = addition.num;
} else {
entryNum = maxNum + 1;
while (usedNumbers.has(entryNum)) entryNum++;
console.warn(
`⚠️ Tracker #${addition.num} already used; assigning #${entryNum} to ` +
`${addition.company} — ${addition.role}. Report link remains ${addition.report}.`,
);
}
usedNumbers.add(entryNum);
if (entryNum > maxNum) maxNum = entryNum;
const pdf = reportNum && pdfIndex.has(String(reportNum)) ? '✅' : addition.pdf;
const newLine = buildRow({
num: entryNum, date: addition.date, company: addition.company, role: addition.role,
via: addition.via || '—',
location: addition.location || '—',
score: addition.score, status: addition.status, pdf,
report: addition.report, notes: addition.notes,
// Write the key on the way in. Backfill is the one-time EXPAND phase for
// rows that predate the column; a row added today must carry its own URL
// or Pass 0 can never match it and dedup stays fuzzy-only for new work.
url: addition.url || '',View on GitHub (pinned to 60398d6549)
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.
Example fix
# before: two TSVs both claim #064 (second one triggers the warning) 064 2026-08-20 Acme Senior ML Engineer Evaluated 4.2/5 ... [064](reports/064-acme-2026-08-20.md) ... # after: reserve first, then write each TSV with its own slot $ node reserve-report-num.mjs --count 2 # -> 064-065 064 2026-08-20 Acme Senior ML Engineer Evaluated 4.2/5 ... 065 2026-08-20 Globex Data Platform Lead Evaluated 4.0/5 ...
Defensive patterns
Strategy: validation
Validate before calling
// Before merging, check every TSV's num against the tracker's used numbers
import { readFileSync, readdirSync } from 'node:fs';
const used = new Set(
[...readFileSync('data/applications.md', 'utf-8').matchAll(/^\|\s*(\d+)\s*\|/gm)].map(m => Number(m[1]))
);
for (const f of readdirSync('batch/tracker-additions').filter(f => f.endsWith('.tsv'))) {
const num = Number(readFileSync(`batch/tracker-additions/${f}`, 'utf-8').split('\t')[0]);
if (used.has(num)) console.error(`${f}: num #${num} already used — reserve a free slot first`);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Could not claim ${count} report slot(s) after ${MAX_RETRIES}
- ⚠️ Could not release report reservation: ${err.message}
- ⚠️ Failed to sync PDF flags: ${e.message}
- Could not release report reservation: ${e.message}
- ${pendingTsvs} pending TSVs in tracker-additions/ (not merge
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/24db861e84833be0.
Report an issue: GitHub.