santifer/career-ops · warning
⚠️ Skipping : header row with no data row
Error message
⚠️ Skipping ${filename}: header row with no data row What it means
merge-tracker.mjs refuses to merge a headed tracker-addition TSV whose first line looks like a header (column labels) but is followed by zero data rows. The header-row-plus-one-data-row shape is the contract parseHeadedAddition (merge-tracker.mjs:781) relies on to resolve every field by name; with no row there is nothing to merge, so it warns and returns null instead of silently merging a stub. This keeps malformed batch output in batch/tracker-additions/ from corrupting applications.md.
Solutions
- Open the flagged TSV and write the single data row beneath the header, one tab-separated cell per label in the same order.
- Delete the empty headed file if no evaluation actually happened, then regenerate it from the evaluation run.
- Re-run the emitter (evaluation worker or batch prompt) that was supposed to write the row, and check its exit status/output this time.
- If the row was written without a trailing newline elsewhere, verify the file actually ends with the data line (some writers buffer output).
Example fix
// before (batch/tracker-additions/042-acme.tsv) num date company role score status pdf report // after num date company role score status pdf report 42 2026-09-16 Acme Senior Backend Engineer 4.2/5 Evaluated ❌ [042](reports/042-acme-2026-09-16.md)
Defensive patterns
Strategy: validation
Validate before calling
const lines = content.split(/\r?\n/).filter(l => l.trim() !== '');
if (lines.length < 2) {
throw new Error(`${file}: headed TSV needs a header row plus exactly one data row (got ${lines.length} line(s))`);
} Type guard
const isWellFormedHeadedTsv = (content) => {
const lines = String(content ?? '').split(/\r?\n/).filter(l => l.trim() !== '');
return lines.length === 2;
}; Prevention
- Write the header and data row in a single atomic write, never header-first with the row appended later.
- Assert lines.length === 2 in any script that emits tracker-addition TSVs.
- Check the emitter's exit status and output size before running merge-tracker.mjs.
- List batch/tracker-additions/ and inspect file line counts before merging.
When it happens
Trigger: A file in batch/tracker-additions/{num}-{company-slug}.tsv contains only the header row (num\tdate\tcompany\t...) with no second line, and merge-tracker.mjs runs over the additions directory. Calling parseTsvContent(content, filename) where content's only non-empty line is recognized by looksLikeTsvHeaderRow.
Common situations: A script or LLM writer created the TSV, errored out before writing the data row, and left an orphan header; a template was copied but never filled in; an editor or shell heredoc truncated the file after the first line; the writer emitted the header and intended to append the row later.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 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 : data rows — one addition per file
- ⚠️ Skipping : invalid entry number
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/de28aac879eb3228.
Report an issue: GitHub.
Appendix: source
Thrown at merge-tracker.mjs:800
const { map, missing, duplicates, unknown } = resolveTsvColumns(splitAdditionCells(lines[0]));
if (duplicates.length) {
console.warn(`⚠️ Skipping ${filename}: header labels the same column twice (${[...new Set(duplicates)].join(', ')}) — refusing to guess which one carries the value`);
return null;
}
if (missing.length) {
console.warn(`⚠️ Skipping ${filename}: header is missing required column(s): ${missing.join(', ')}`);
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.View on GitHub (pinned to aac998c7ed)