santifer/career-ops · error

⚠️ Skipping : header labels the same column twice ( ) —…

Error message

⚠️  Skipping ${filename}: header labels the same column twice (${[...new Set(duplicates)].join(', ')}) — refusing to guess which one carries the value

What it means

parseHeadedAddition() in merge-tracker.mjs resolves TSV header labels to fields via resolveTsvColumns(). If the same required/optional label appears in two columns (e.g. two 'score' headers), the value's column is undecidable, so the file is refused with this warning and nothing is merged — deliberate 'refuse to guess' behavior to avoid transposed rows.

Solutions

  1. Remove the duplicate header column (and its value) so each label appears once
  2. Check for alias spellings that map to the same field and keep only one
  3. Re-run merge-tracker.mjs after fixing the header

Example fix

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

Strategy: validation

Validate before calling

const labels = headerRow.split('\t');
const dupes = labels.filter((l, i) => labels.indexOf(l) !== i);
if (dupes.length) throw new Error('Duplicate header labels: ' + dupes.join(', '));

Prevention

When it happens

Trigger: A batch/tracker-additions/*.tsv whose header row lists the same label twice, including alias spellings that resolve to the same field (e.g. 'company' and a synonym alias both present).

Common situations: Hand-building a header row and duplicating a column; template concatenation errors; adding a synonym alias column next to the canonical one.

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


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

Appendix: source

Thrown at merge-tracker.mjs:784

/**
 * Parse a HEADED addition: a row of column labels followed by exactly one data
 * row. Fields resolve by NAME, so no column order is privileged and the
 * 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;
  }

View on GitHub (pinned to aac998c7ed)