santifer/career-ops · error

⚠️ Skipping : header is missing required column(s)

Error message

⚠️  Skipping ${filename}: header is missing required column(s): ${missing.join(', ')}

What it means

parseHeadedAddition() validates that every REQUIRED column label (num, date, company, role, score, status, pdf, report) is present in the TSV header. If any are missing, the file is skipped entirely with this warning listing the absent labels — a malformed addition is never merged partially.

Solutions

  1. Add the missing required column(s) named in the warning to the header row and fill a value in the data row (use a sentinel like N/A for score when unscored)
  2. Copy the header template from AGENTS.md / DATA_CONTRACT.md rather than writing it by hand
  3. Re-run merge-tracker.mjs after fixing

Example fix

// before
num\tdate\tcompany\trole\tscore\tstatus\tnotes
// after
num\tdate\tcompany\trole\tscore\tstatus\tpdf\treport\tnotes
Defensive patterns

Strategy: validation

Validate before calling

const REQUIRED = ['num','date','company','role','score','status','pdf','report'];
const present = headerRow.split('\t').map(s => s.trim().toLowerCase());
const missing = REQUIRED.filter(r => !present.includes(r));
if (missing.length) throw new Error('Missing required columns: ' + missing.join(', '));

Prevention

When it happens

Trigger: A batch/tracker-additions/*.tsv whose header omits one or more required labels — e.g. forgetting the pdf column, or writing only a partial header row.

Common situations: Hand-written TSV files missing columns like pdf or report; trimmed-down headers someone assumed were optional; copy-paste that dropped a column.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/3dfa85b0501703a8. Report an issue: GitHub.

Appendix: source

Thrown at merge-tracker.mjs:788

 * score/status pair needs no content sniffing.
 *
 * This is the form that has no undecidable case. `resolveScoreStatus` cannot
 * order a discarded, never-scored row (`—` is both a score sentinel and a
 * status meaning Discarded), so the headerless path refuses it; a header row
 * answers it outright (#3517).
 *
 * @param {string[]} lines - Non-empty lines of the addition file.
 * @param {string} filename - Source filename used in warning messages.
 * @returns {object|null} Parsed tracker addition, or null when malformed.
 */
function parseHeadedAddition(lines, filename) {
  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.

View on GitHub (pinned to aac998c7ed)