santifer/career-ops · warning

⚠️ Additions carry a URL but this tracker has no URL column

Error message

⚠️  Additions carry a URL but this tracker has no URL column — URL dedup is INACTIVE (fuzzy fallback). Add a `URL` header column to enable it.

What it means

Deduplication Pass 0 matches additions to tracker rows by exact normalized posting URL (tracking params stripped, host lowercased). That only works when the tracker has a URL header column; without it the merge silently degrades to report-number/entry-number/fuzzy tiers. This once-per-run warning (warnedNoUrlCol) announces that deterministic URL dedup is inactive.

Source

Thrown at merge-tracker.mjs:995

  const content = readFileSync(join(ADDITIONS_DIR, file), 'utf-8').trim();
  const addition = parseTsvContent(content, file);
  if (!addition) { skipped++; continue; }

  // A via= tag can only be stored if the tracker has a Via column — warn
  // instead of dropping the channel silently (#1596). Clear the value too:
  // existing rows parse with via='' on this layout, so a set addition.via would
  // make the cross-channel duplicate guard see a channel mismatch and add a
  // second ? row instead of updating the same-agency re-blast.
  if (addition.via && COLMAP.via == null) {
    console.warn(`⚠️  ${file}: carries via=${addition.via} but the tracker has no Via column — value dropped. Add it with: node merge-tracker.mjs --migrate-via`);
    addition.via = '';
  }

  // If additions carry a URL but the tracker has no URL
  // column, deterministic Pass 0 cannot engage and dedup silently falls back to
  // the fuzzy tiers. Warn once rather than degrade invisibly.
  if (addition.url && COLMAP.url == null && !warnedNoUrlCol) {
    console.warn('⚠️  Additions carry a URL but this tracker has no URL column — URL dedup is INACTIVE (fuzzy fallback). Add a `URL` header column to enable it.');
    warnedNoUrlCol = true;
  }

  // Normalize the report link to be relative to the tracker file's directory.
  // The TSV convention carries a root-relative `reports/...` link; rewrite it
  // so it resolves correctly when clicked from applications.md (see #760).
  addition.report = normalizeReportLink(addition.report);

  // Derive the key from the linked report when the TSV did not carry one, so a
  // row added through the normal nine-column flow is born WITH its key. This
  // must run before the dedup below reads addition.url — deriving it afterwards
  // would populate the column while leaving Pass 0 nothing to match on, which is
  // the original bug with extra steps.
  if (!addition.url) addition.url = resolveReportUrl(addition.report).url;

  // Check for duplicate by:
  // 0. Exact normalized posting URL (deterministic, authoritative)
  // 1. Exact report number match

View on GitHub (pinned to 60398d6549)

Solutions

  1. Add a `URL` header column to data/applications.md so Pass 0 can engage
  2. Backfill URLs for existing rows from their reports with `node merge-tracker.mjs --backfill-urls`
  3. Keep including the posting URL as the trailing field in new TSVs — it is the deterministic dedup key

Example fix

# before (data/applications.md header)
| # | Date | Company | Role | Score | Status | PDF | Report | Notes |

# after
| # | Date | Company | Role | Score | Status | PDF | Report | Notes | URL |
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
function trackerHasUrlColumn(trackerPath) {
  const header = readFileSync(trackerPath, 'utf8').split('\n').find(l => l.startsWith('|'));
  return !!header && /\burl\b/i.test(header);
}

Prevention

When it happens

Trigger: At least one additions file carries a trailing http(s):// posting URL while data/applications.md's header row lacks a URL column — addition.url is truthy, COLMAP.url == null, and the single warning fires before fuzzy-tier matching takes over.

Common situations: Long-lived tracker predating the URL feature; trackers built by hand from the 9-column template; after restoring applications.md from an old backup.

Related errors


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