{"record":{"id":"d2aab5bbef4c1375","repo":"santifer/career-ops","slug":"skipping-filename-ambiguous-extra-fields","errorCode":null,"errorMessage":"⚠️  Skipping ${filename}: ambiguous extra fields [${extras.join(', ')}] — expected at most one \"via=Firm\" tag, one location and one URL","messagePattern":"⚠️  Skipping (.+?): ambiguous extra fields \\[(.+?)\\] — expected at most one \"via=Firm\" tag, one location and one URL","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":636,"sourceCode":"function parseTsvExtras(parts, filename) {\n  // Drop placeholders outright. A generator emitting the documented trailing\n  // `url` field for a posting that has none writes \"N/A\"/\"TBD\"/\"—\", and by shape\n  // that is not a URL — it would fall into the untagged bucket and be recorded\n  // as the row's LOCATION. An absent value must read as absent, not as some\n  // other column's content.\n  const PLACEHOLDER = /^(n\\/?a|tbd|none|null|-|—|–)$/i;\n  const extras = parts.slice(9)\n    .map(s => String(s).trim())\n    .filter(s => s !== '' && !PLACEHOLDER.test(s));\n  const viaTags = extras.filter(s => /^via=/i.test(s));\n  // Classify trailing fields by SHAPE, not position. A URL is\n  // unambiguous (starts with http(s)://), so the posting URL and an older\n  // location cell are order-independent and a row carrying both is not read as\n  // two ambiguous locations. Location-only rows keep working untouched.\n  const urls = extras.filter(s => !/^via=/i.test(s) && /^https?:\\/\\//i.test(s));\n  const untagged = extras.filter(s => !/^via=/i.test(s) && !/^https?:\\/\\//i.test(s));\n  if (viaTags.length > 1 || untagged.length > 1 || urls.length > 1) {\n    console.warn(`⚠️  Skipping ${filename}: ambiguous extra fields [${extras.join(', ')}] — expected at most one \"via=Firm\" tag, one location and one URL`);\n    return null;\n  }\n  return {\n    via: viaTags.length ? viaTags[0].replace(/^via=/i, '').trim() : '',\n    location: untagged[0] || '',\n    url: urls[0] || '',\n  };\n}\n\nfunction parseTsvContent(content, filename) {\n  content = content.trim();\n  if (!content) return null;\n\n  let parts;\n  let addition;\n\n  // Detect pipe-delimited (markdown table row)\n  if (content.startsWith('|')) {","sourceCodeStart":618,"sourceCodeEnd":654,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/merge-tracker.mjs#L618-L654","documentation":"parseTsvExtras() in merge-tracker.mjs classifies every field after the ninth column by shape: at most one `via=Firm` tag, at most one URL (http(s):// prefix), at most one untagged location. Any other combination — multiple via tags, multiple locations, multiple URLs, or other stray text — is ambiguous, so the whole file is skipped rather than guessed at.","triggerScenarios":"A TSV row appending e.g. `...notes\tvia=Hays\tvia=Adecco` or `...notes\tBerlin\tRemote` — viaTags.length > 1 or untagged.length > 1 trips the guard, the warning names the offending fields, and parseTsvContent returns null so the addition is skipped.","commonSituations":"Hand-extended rows adding both a location and a comment; concatenating two TSV exports whose extra columns differ; a note containing tab characters splitting into phantom fields.","solutions":["Reduce extras to at most one via= tag, one location, and one URL per row","Move additional commentary into the notes column (field 9) instead of appending new columns","Ensure the file is genuine tab-separated with no stray tabs inside notes"],"exampleFix":"# before\n042\t2026-08-20\tAcme\tSRE\tInterview\t4.2/5\t✅\t[042](reports/042-acme-2026-08-20.md)\tnote\tvia=Hays\tvia=Adecco\n\n# after\n042\t2026-08-20\tAcme\tSRE\tInterview\t4.2/5\t✅\t[042](reports/042-acme-2026-08-20.md)\tnote\tvia=Hays","handlingStrategy":"validation","validationCode":"function extrasAreUnambiguous(parts) {\n  const PLACEHOLDER = /^(n\\/?a|tbd|none|null|-|—|–)$/i;\n  const extras = parts.slice(9).map(s => s.trim()).filter(s => s !== '' && !PLACEHOLDER.test(s));\n  const count = re => extras.filter(re).length;\n  return count(/^via=/i) <= 1 && count(/^https?:\\/\\//i) <= 1 &&\n         count(s => !/^via=/i.test(s) && !/^https?:\\/\\//i.test(s)) <= 1;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Stick to the documented extras: one via= tag, one location, one trailing URL","Put commentary in the notes column, never in appended columns","Quote fields containing tabs or strip tabs before writing TSVs"],"tags":["tracker","tsv-parsing","merge","row-schema","warning"],"backgroundTag":"row-schema-ambiguous","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}