santifer/career-ops · warning
⚠️ Skipping : a URL sits under " " while the "url" cell is…
Error message
⚠️ 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 What it means
When a headed row is one or more cells short AND its missing labels include the optional 'url' column, the parser checks for a typed corroborating signal: an http(s):// value sitting under a different label while the 'url' label has no cell. That proves the optional cells are shifted one or more columns left of their labels — the URL would be stored as notes and the dedup key left empty. The file is skipped rather than merged with the wrong mapping.
Solutions
- Rewrite the data row so it has exactly one cell per header label, writing an empty cell (consecutive tabs) for every optional value you omit — the shifted URL then lands under 'url'.
- Alternatively drop the 'url' column from the header if you never send URLs, or use a fully-populated 10-cell row.
- Check the emitter's field ordering: it must emit cells in exactly the header's label order with placeholders for skipped optionals.
- After fixing, re-run merge-tracker.mjs and confirm the row merges with the URL in the tracker's URL column.
Example fix
// before (location omitted without empty cell — notes/url shifted left) num date company role score status pdf report notes via location url 42 ... Acme Backend 4.2/5 Evaluated ❌ [042](reports/042.md) Remote friendly https://jobs.acme.com/123 // after (empty cell for the omitted optional) 42 ... Acme Backend 4.2/5 Evaluated ❌ [042](reports/042.md) Remote friendly https://jobs.acme.com/123
Defensive patterns
Strategy: validation
Validate before calling
const header = lines[0].split('\t');
const cells = lines[1].split('\t');
if (cells.length !== header.length) {
throw new Error(`${file}: ${header.length - cells.length} cell(s) short — write an empty cell (extra tab) for every omitted optional value`);
} Prevention
- Always write an empty cell (consecutive tabs) for an omitted optional value — never skip the cell.
- Build the row programmatically from the header labels array so the length always matches.
- Never fall back to positional writing under a headed template; the header defines the order.
- Check that spreadsheet/CSV exports preserve empty cells instead of collapsing them.
When it happens
Trigger: parseHeadedAddition (merge-tracker.mjs:856-863): map.url exists in the header, shortLabels includes 'url' (the row has fewer cells than the url column index), and some non-url-labelled cell matches /^https?:\/\/\S*$/i. E.g. a 9-cell row for a 10-label header where notes and url were both written but location was omitted without an empty cell.
Common situations: A writer dropped an interior optional cell (location or via) instead of writing an empty cell for it, sliding notes/url left; headerless-style positional writing was used against a headed template; a spreadsheet export skipped empty cells entirely.
Related errors
- ⚠️ 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
- ⚠️ Skipping : invalid entry number
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/dd9611321c682c4b.
Report an issue: GitHub.
Appendix: source
Thrown at merge-tracker.mjs:861
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;
}
}
// Write-canonical: the tracker stores scores unbolded (verify-pipeline
// rejects bold scores), so strip any markdown bold from the incoming cell.
const score = at('score').replace(/\*\*/g, '').trim();
if (!looksLikeScoreCell(score)) {
// Corroboration, not disambiguation: the header already said which column
// this is, so this can no longer pick an order — it only asks whether the
// labelled cell holds what the label promises. It stays a hard gate because
// the value it catches is an emitter that wrote its VALUES in one order and
// its LABELS in another, which is the silent swap in a new costume. The
// headerless path already refuses a score cell of this shape (neither cell
// score-like → undecidable), so this is the same strictness, not new.
console.warn(`⚠️ Skipping ${filename}: the column labelled "score" reads "${score}", which is not a score or a documented sentinel (X.X/5, N/A, —, -) — check the header labels match the value order`);
return null;
}View on GitHub (pinned to aac998c7ed)