santifer/career-ops · warning
⚠️ Skipping : data rows — one addition per file
Error message
⚠️ Skipping ${filename}: ${dataLines.length} data rows — one addition per file What it means
The headed addition parser enforces 'exactly one data row per file'. Every caller of the merge loop assumes one addition per TSV, so a file with 2+ data rows is rejected outright rather than merging the first row and silently dropping the rest. The warn-and-null keeps extra evaluations from being lost without a trace.
Solutions
- Split the file: write each data row into its own {num}-{company-slug}.tsv under batch/tracker-additions/, keeping the header row in each.
- Re-run the offending emitter with instructions to produce exactly one data row per file (the batch prompt's documented contract).
- If the extra rows are accidental duplicates, trim the file to the single correct row.
- After fixing, re-run node merge-tracker.mjs and confirm the warning is gone and the row merged.
Example fix
// before (one file, two rows) num date company role score status pdf report 42 2026-09-16 Acme Backend 4.2/5 Evaluated ❌ [042](reports/042.md) 43 2026-09-16 Globex Backend 3.9/5 Evaluated ❌ [043](reports/043.md) // after: 042-acme.tsv has row 42, 043-globex.tsv has row 43, each with the same header
Defensive patterns
Strategy: validation
Validate before calling
const dataRows = content.split(/\r?\n/).filter(l => l.trim() !== '').slice(1);
if (dataRows.length !== 1) {
throw new Error(`${file}: exactly one data row per file required (got ${dataRows.length})`);
} Type guard
const isSingleAdditionFile = (content) => String(content ?? '').split(/\r?\n/).filter(l => l.trim() !== '').length === 2;
Prevention
- Never batch multiple evaluations into one TSV — one {num}-{company-slug}.tsv per addition, always.
- Avoid concatenating addition files with cat or >> — each file is a standalone addition.
- Add a post-write assertion of exactly two lines (header + one row) in your emitter.
- Review batch worker output for files with 3+ lines before running merge-tracker.mjs.
When it happens
Trigger: parseHeadedAddition (merge-tracker.mjs:803) sees lines.slice(1).length > 1 — a headed TSV in batch/tracker-additions/ carries two or more non-empty lines after the header. Typically a writer batched several evaluations into one file, violating the one-addition-per-file contract.
Common situations: An LLM batch worker wrote all of a run's rows into one TSV instead of one file per evaluation; someone concatenated TSVs with cat; a spreadsheet export flattened multiple rows into one file; a copy-paste loop appended rows to the same file across runs.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- ⚠️ Skipping : a URL sits under " " while the "url" cell is…
- ⚠️ Skipping : cannot tell score from status in columns 5–6…
- ⚠️ Skipping : data row has field(s), missing the required…
- ⚠️ Skipping : header row with no data row
- ⚠️ Skipping : invalid entry number
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/0038c4cbe0ae1e8a.
Report an issue: GitHub.
Appendix: source
Thrown at merge-tracker.mjs:807
return null;
}
if (unknown.length) {
// Not fatal: an unrecognized label is a column this version has no meaning
// for, and dropping it loses nothing the tracker could store anyway. Say so
// rather than swallowing it, since the usual cause is a typo'd label.
console.warn(`⚠️ ${filename}: ignoring unrecognized header column(s): ${unknown.join(', ')}`);
}
const dataLines = lines.slice(1);
if (dataLines.length === 0) {
console.warn(`⚠️ Skipping ${filename}: header row with no data row`);
return null;
}
if (dataLines.length > 1) {
// One addition per file is the contract every caller assumes (the merge
// loop takes one addition per TSV). Rejecting is louder than merging the
// first row and dropping the rest, which would lose evaluations silently.
console.warn(`⚠️ Skipping ${filename}: ${dataLines.length} data rows — one addition per file`);
return null;
}
const parts = splitAdditionCells(dataLines[0]);
// Only the REQUIRED cells must actually be there. A row whose trailing
// optional value is empty is routinely written without its final tab —
// openrouter-runner emits `…\treport\t\n` for an absent note, and an LLM
// writer asked for "leave the last field empty" often just stops — so
// demanding a cell for every label would reject canonical rows. Absent and
// empty read the same here, which is what the batch and web prompts already
// promise their writers.
//
// This is not the defense against a SHIFTED row: an omitted interior cell
// slides every later value one column left, which the score corroboration
// below catches by content regardless of the row's width.
const shortLabels = Object.entries(map).filter(([, i]) => i >= parts.length).map(([k]) => k);
const missingRequiredCells = shortLabels.filter(k => TSV_REQUIRED_FIELDS.includes(k));
if (missingRequiredCells.length) {View on GitHub (pinned to aac998c7ed)