santifer/career-ops · warning

⚠️ Skipping ${filename}: cannot tell score from status in c

Error message

⚠️  Skipping ${filename}: cannot tell score from status in columns 5–6 ("${parts[4]}" | "${parts[5]}") — refusing to merge a possible column swap

What it means

Because batch TSVs write (status, score) while applications.md stores (score, status), merge-tracker.mjs identifies columns 5-6 by content, not position (guard #1427). resolveScoreStatus() fails when neither cell is recognizable as a score pattern (X.X/5 or the N/A / — / - sentinels), and the row is refused rather than merged with possibly swapped data.

Source

Thrown at merge-tracker.mjs:667

  let parts;
  let addition;

  // Detect pipe-delimited (markdown table row)
  if (content.startsWith('|')) {
    parts = content.split('|').map(s => s.trim());
    if (parts[0] === '') parts.shift();
    if (parts[parts.length - 1] === '') parts.pop();
    if (parts.length < 8) {
      console.warn(`⚠️  Skipping malformed pipe-delimited ${filename}: ${parts.length} fields`);
      return null;
    }
    // Format: num | date | company | role | score | status | pdf | report | notes [| location]
    // Identify score vs status by content, not position, so a swapped row can't
    // merge silently (#1427).
    const resolved = resolveScoreStatus(parts[4], parts[5]);
    if (!resolved) {
      console.warn(`⚠️  Skipping ${filename}: cannot tell score from status in columns 5–6 ("${parts[4]}" | "${parts[5]}") — refusing to merge a possible column swap`);
      return null;
    }
    addition = {
      num: parseInt(parts[0]),
      date: parts[1],
      company: parts[2],
      role: parts[3],
      // Write-canonical: the tracker stores scores unbolded (verify-pipeline
      // rejects bold scores), so strip any markdown bold from the incoming cell.
      score: resolved.score.replace(/\*\*/g, '').trim(),
      status: validateStatus(resolved.status),
      pdf: parts[6],
      report: parts[7],
      notes: parts[8] || '',
    };
    const extras = parseTsvExtras(parts, filename);
    if (!extras) return null;
    Object.assign(addition, extras);

View on GitHub (pinned to 60398d6549)

Solutions

  1. Format the score cell as X.X/5 or one of the sentinels N/A, — (em dash), or - (hyphen)
  2. Keep the status cell a canonical status value so the pair is decidable in either column order
  3. Re-run the merge and verify the row lands with score and status in the right columns

Example fix

# before (columns 5-6)
| 042 | 2026-08-20 | Acme | SRE | Interview | pending | ... |

# after
| 042 | 2026-08-20 | Acme | SRE | Interview | 4.2/5 | ... |
Defensive patterns

Strategy: validation

Validate before calling

const SCORE_CELL = /^(\d+(\.\d+)?\/5|n\/a|—|-)$/i;
function columnsFiveSixResolvable(a, b) {
  return SCORE_CELL.test(a.trim()) || SCORE_CELL.test(b.trim());
}

Prevention

When it happens

Trigger: A pipe row whose columns 5-6 are e.g. 'Interview' | 'high' — neither matches a score pattern, so resolveScoreStatus returns null and the skip warning quotes both cells. A blank score also fails: blank is not one of the recognized sentinels.

Common situations: Backfilled rows left with an empty score cell instead of N/A/—/-; creative placeholders like 'n/a yet' or 'TBD soon'; genuinely swapped columns where both cells contain status-like text.

Related errors


AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20). Data as JSON: /api/errors/1d0adca61a467541. Report an issue: GitHub.