santifer/career-ops · warning
⚠️ Skipping : required cell(s) present but empty: — use the…
Error message
⚠️ Skipping ${filename}: required cell(s) present but empty: ${blankRequiredCells.join(', ')} — use the documented sentinel (— / N/A) rather than a blank cell What it means
A required cell that is present in the data row but blank (empty after trimming) is rejected: blank is never the way to say 'none' in this format. Every required field has a documented value or sentinel ('—' / 'N/A' for score, '—' for report, '❌' for pdf). Left unchecked, an empty status would reach validateStatus(''), which coerces to 'Evaluated' — recording an evaluation state the row never claimed.
Solutions
- Replace each blank required cell named in the warning with its documented value or sentinel: score → 'X.X/5', 'N/A', '—' or '-'; status → a canonical state from templates/states.yml; pdf → '✅' or '❌'; report → the markdown link or '—'; num/date/company/role → real values.
- For a backfilled row with no evaluation, use the score sentinel 'N/A', '—' or '-' explicitly rather than leaving the cell empty.
- Fix the emitter to substitute sentinels instead of empty strings for unknown values.
- Re-run node merge-tracker.mjs after editing and confirm the row merges.
Example fix
// before (blank status and score cells) 42 2026-09-16 Acme Backend Engineer ❌ [042](reports/042.md) // after 42 2026-09-16 Acme Backend Engineer 4.2/5 Evaluated ❌ [042](reports/042.md) // or for a never-scored backfill: 42 2026-09-16 Acme Backend Engineer N/A Applied ❌ —
Defensive patterns
Strategy: validation
Validate before calling
const REQUIRED = ['num','date','company','role','score','status','pdf','report'];
const blank = REQUIRED.filter(k => String(cells[header.indexOf(k)] ?? '').trim() === '');
if (blank.length) throw new Error(`${file}: blank required cell(s) ${blank.join(', ')} — use sentinels (— / N/A / ❌)`); Prevention
- Substitute documented sentinels ('—'/'N/A' for score, '—' for report, '❌' for pdf) whenever a value is unknown — never an empty string.
- For backfilled rows with no evaluation, always write the score sentinel N/A, — or -.
- Default optional values in the emitter to sentinels instead of ''.
- Grep your additions directory for rows with consecutive tabs before merging: grep -P '\t\t' batch/tracker-additions/*.
When it happens
Trigger: parseHeadedAddition (merge-tracker.mjs:843) finds TSV_REQUIRED_FIELDS entries where at(k) === '' — the cell exists at the right column index but holds only whitespace or nothing (e.g. a row like 42\t\tAcme\t... with an empty date cell).
Common situations: An LLM writer left a placeholder it then stripped; a spreadsheet export wrote empty cells for unknown values; a script interpolated an unset shell variable (\t${SCORE}\t producing an empty cell); a human hand-edited the row and deleted a value instead of substituting the sentinel.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 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 : header row with no data row
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/1238916181e19947.
Report an issue: GitHub.
Appendix: source
Thrown at merge-tracker.mjs:844
console.warn(`⚠️ Skipping ${filename}: data row has ${parts.length} field(s), missing the required cell(s): ${missingRequiredCells.join(', ')}`);
return null;
}
const at = (k) => (map[k] != null ? String(parts[map[k]] ?? '').trim() : '');
const optional = (k) => {
const v = at(k);
return PLACEHOLDER_CELL.test(v) ? '' : v;
};
// A required cell that is PRESENT but blank is a writer that did not supply
// the value, and blank is never the way to say "none" here: every required
// field has a documented value, and the "no data" cases have sentinels
// (`—` / `N/A` for score, `—` for report, `❌` for pdf). Left unchecked, an
// empty status reaches validateStatus(''), which warns and returns
// "Evaluated" — recording a real evaluation state the row never claimed.
const blankRequiredCells = TSV_REQUIRED_FIELDS.filter(k => at(k) === '');
if (blankRequiredCells.length) {
console.warn(`⚠️ Skipping ${filename}: required cell(s) present but empty: ${blankRequiredCells.join(', ')} — use the documented sentinel (— / N/A) rather than a blank cell`);
return null;
}
// The optional tail can only be trusted when a short row's missing cells are
// genuinely its LAST ones. Position cannot tell "notes omitted, url written"
// from "notes written, url omitted" — both are one cell short — so the one
// TYPED optional column is the corroboration. A cell that IS a URL, sitting
// under a label that is not `url` while the `url` label has no cell at all,
// means every optional value is one column left of its label. Merging that
// stores the posting URL as the row's NOTES and leaves the dedup key empty,
// which is precisely the mapping the header exists to protect.
if (map.url != null && shortLabels.includes('url')) {
const misplacedUrl = Object.entries(map)
.filter(([k, i]) => k !== 'url' && i < parts.length && /^https?:\/\/\S*$/i.test(String(parts[i] ?? '').trim()))
.map(([k]) => k);
if (misplacedUrl.length) {
console.warn(`⚠️ Skipping ${filename}: a URL sits under "${misplacedUrl.join('", "')}" while the "url" cell is absent — the optional cells are shifted; write an empty cell for each optional value you omit`);
return null;View on GitHub (pinned to aac998c7ed)