{"record":{"id":"0038c4cbe0ae1e8a","repo":"santifer/career-ops","slug":"skipping-filename-datalines-length-data-rows-one-addition","errorCode":null,"errorMessage":"⚠️  Skipping ${filename}: ${dataLines.length} data rows — one addition per file","messagePattern":"⚠️  Skipping (.+?): (.+?) data rows — one addition per file","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":807,"sourceCode":"    return null;\n  }\n  if (unknown.length) {\n    // Not fatal: an unrecognized label is a column this version has no meaning\n    // for, and dropping it loses nothing the tracker could store anyway. Say so\n    // rather than swallowing it, since the usual cause is a typo'd label.\n    console.warn(`⚠️  ${filename}: ignoring unrecognized header column(s): ${unknown.join(', ')}`);\n  }\n\n  const dataLines = lines.slice(1);\n  if (dataLines.length === 0) {\n    console.warn(`⚠️  Skipping ${filename}: header row with no data row`);\n    return null;\n  }\n  if (dataLines.length > 1) {\n    // One addition per file is the contract every caller assumes (the merge\n    // loop takes one addition per TSV). Rejecting is louder than merging the\n    // first row and dropping the rest, which would lose evaluations silently.\n    console.warn(`⚠️  Skipping ${filename}: ${dataLines.length} data rows — one addition per file`);\n    return null;\n  }\n\n  const parts = splitAdditionCells(dataLines[0]);\n  // Only the REQUIRED cells must actually be there. A row whose trailing\n  // optional value is empty is routinely written without its final tab —\n  // openrouter-runner emits `…\\treport\\t\\n` for an absent note, and an LLM\n  // writer asked for \"leave the last field empty\" often just stops — so\n  // demanding a cell for every label would reject canonical rows. Absent and\n  // empty read the same here, which is what the batch and web prompts already\n  // promise their writers.\n  //\n  // This is not the defense against a SHIFTED row: an omitted interior cell\n  // slides every later value one column left, which the score corroboration\n  // below catches by content regardless of the row's width.\n  const shortLabels = Object.entries(map).filter(([, i]) => i >= parts.length).map(([k]) => k);\n  const missingRequiredCells = shortLabels.filter(k => TSV_REQUIRED_FIELDS.includes(k));\n  if (missingRequiredCells.length) {","sourceCodeStart":789,"sourceCodeEnd":825,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/merge-tracker.mjs#L789-L825","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (one file, two rows)\nnum\tdate\tcompany\trole\tscore\tstatus\tpdf\treport\n42\t2026-09-16\tAcme\tBackend\t4.2/5\tEvaluated\t❌\t[042](reports/042.md)\n43\t2026-09-16\tGlobex\tBackend\t3.9/5\tEvaluated\t❌\t[043](reports/043.md)\n\n// after: 042-acme.tsv has row 42, 043-globex.tsv has row 43, each with the same header","handlingStrategy":"validation","validationCode":"const dataRows = content.split(/\\r?\\n/).filter(l => l.trim() !== '').slice(1);\nif (dataRows.length !== 1) {\n  throw new Error(`${file}: exactly one data row per file required (got ${dataRows.length})`);\n}","typeGuard":"const isSingleAdditionFile = (content) =>\n  String(content ?? '').split(/\\r?\\n/).filter(l => l.trim() !== '').length === 2;","tryCatchPattern":null,"preventionTips":["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."],"tags":["tsv","parsing","merge-tracker","batch"],"backgroundTag":"schema-validation-failed","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}