santifer/career-ops · error

⚠️ Skipping : ambiguous extra fields [ ] — expected at most…

Error message

⚠️  Skipping ${filename}: ambiguous extra fields [${extras.join(', ')}] — expected at most one "via=Firm" tag, one location and one URL

What it means

parseTsvExtras() in merge-tracker.mjs parses optional trailing fields after the 9 base columns: at most one 'via=Firm' tag, one untagged location, and one URL. If any category appears more than once (e.g. two untagged extras or two URLs), the row is genuinely ambiguous, the whole file is skipped with this warning, and the addition is NOT merged.

Solutions

  1. Drop or merge the duplicate extras so the row carries at most one untagged location, one URL, and one via= tag
  2. Tag the agency explicitly as via=Firm so it is not mistaken for a second location
  3. Prefer the header form (with named columns) so extra fields can't be ambiguous
  4. Fix the TSV and re-run merge-tracker.mjs; the file is skipped entirely until then

Example fix

// before (headerless tail): ...notes	Berlin	Hays	url1
// after: ...notes	Berlin	via=Hays	https://jobs.acme.com/123
Defensive patterns

Strategy: validation

Validate before calling

function extrasAreUnambiguous(extras) {
  const via = extras.filter(s => /^via=/i.test(s)).length;
  const urls = extras.filter(s => /^https?:\/\//i.test(s)).length;
  const untagged = extras.filter(s => !/^via=/i.test(s) && !/^https?:\/\//i.test(s)).length;
  return via <= 1 && urls <= 1 && untagged <= 1;
}

Prevention

When it happens

Trigger: A tracker-addition TSV row with 10+ positional fields where two extras are untagged non-URL strings (two locations), or two http(s) URLs appear, or two via= tags are present — any of viaTags.length > 1, untagged.length > 1, or urls.length > 1.

Common situations: Appending extra metadata (location plus a second free-text field) to legacy headerless rows; pasting both the posting URL and a tracking-link URL; forgetting the mandatory via= tag so the agency name counts as an untagged location alongside the real location.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at merge-tracker.mjs:731

 */
function parseTsvExtras(parts, filename) {
  // Drop placeholders outright. A generator emitting the documented trailing
  // `url` field for a posting that has none writes "N/A"/"TBD"/"—", and by shape
  // that is not a URL — it would fall into the untagged bucket and be recorded
  // as the row's LOCATION. An absent value must read as absent, not as some
  // other column's content.
  const extras = parts.slice(9)
    .map(s => String(s).trim())
    .filter(s => s !== '' && !PLACEHOLDER_CELL.test(s));
  const viaTags = extras.filter(s => /^via=/i.test(s));
  // Classify trailing fields by SHAPE, not position. A URL is
  // unambiguous (starts with http(s)://), so the posting URL and an older
  // location cell are order-independent and a row carrying both is not read as
  // two ambiguous locations. Location-only rows keep working untouched.
  const urls = extras.filter(s => !/^via=/i.test(s) && /^https?:\/\//i.test(s));
  const untagged = extras.filter(s => !/^via=/i.test(s) && !/^https?:\/\//i.test(s));
  if (viaTags.length > 1 || untagged.length > 1 || urls.length > 1) {
    console.warn(`⚠️  Skipping ${filename}: ambiguous extra fields [${extras.join(', ')}] — expected at most one "via=Firm" tag, one location and one URL`);
    return null;
  }
  return {
    via: viaTags.length ? viaTags[0].replace(/^via=/i, '').trim() : '',
    location: untagged[0] || '',
    url: urls[0] || '',
  };
}

/**
 * Parse an addition's tracker number, rejecting anything that is not a whole
 * positive integer.
 *
 * `parseInt` is the wrong tool: it stops at the first character that cannot
 * continue a number, so `17oops` and `17.5` both become 17 and the row merges
 * under a tracker number the file never claimed. The guards downstream cannot
 * see it — by then the damage is a perfectly valid-looking number.
 *

View on GitHub (pinned to aac998c7ed)