santifer/career-ops · info

⚠️ Keep #${a.num} and #${b.num}: exact-title match but adva

Error message

⚠️  Keep #${a.num} and #${b.num}: exact-title match but advanced status requires exact report identity

What it means

dedup-tracker.mjs is telling you it deliberately did NOT merge two tracker rows whose normalized titles match exactly, because at least one row has an advanced status (Applied, Responded, Interview, Offer, Hired). Deleting a lower-scored exact-title sibling could destroy real application state — status, report link, notes — so both rows are kept unless they point at the exact same report identity (sameReportIdentity). The warning fires once per pair via the protectedTitlePairs set.

Source

Thrown at dedup-tracker.mjs:230

 *
 * @param {object} a - First parsed applications.md row.
 * @param {object} b - Second parsed applications.md row.
 * @returns {boolean} True when dedup may cluster the two rows as duplicates.
 */
function roleMatch(a, b) {
  if (sameReportIdentity(a, b)) return true;
  if (normalizeRole(a.role) !== normalizeRole(b.role)) return false;

  // Exact-title duplicates that have entered the real application pipeline are
  // kept separate. A user may already have applied to one row; deleting it
  // because a higher-scored exact-title sibling exists would lose status,
  // report, and notes. Keep both unless the rows point to the exact same
  // report identity.
  if (isAdvancedStatus(a.status) || isAdvancedStatus(b.status)) {
    const key = pairKey(a, b);
    if (!protectedTitlePairs.has(key)) {
      protectedTitlePairs.add(key);
      console.warn(`⚠️  Keep #${a.num} and #${b.num}: exact-title match but advanced status requires exact report identity`);
    }
    return false;
  }

  return true;
}

/**
 * Parse a tracker score cell into a numeric value for keeper selection.
 *
 * Scores may include Markdown bolding or a `/5` suffix. Dedup only needs the
 * numeric part so it can keep the highest-scored duplicate row in a cluster.
 *
 * @param {string} s - Raw score cell such as `4.3/5` or `**4.3/5**`.
 * @returns {number} Parsed score, or 0 when no number is present.
 */
function parseScore(s) {
  const m = s.replace(/\*\*/g, '').match(/([\d.]+)/);

View on GitHub (pinned to 60398d6549)

Solutions

  1. If both rows are the SAME application, merge by hand: keep the advanced-status row, copy the report link and notes onto it, then delete the other with a careful manual edit.
  2. If they are genuinely different requisitions, add the req/job IDs to both rows' notes (e.g. 'req JR-10423') so dedup and other tooling can tell them apart permanently.
  3. Otherwise do nothing — keeping both is intentional data-safety behavior.

Example fix

# before — two rows, exact same title, one already applied
| 42 | 2026-08-01 | Acme | Data Engineer | 4.2/5 | Applied | ❌ | [42](reports/042-acme-2026-08-01.md) | |
| 87 | 2026-08-18 | Acme | Data Engineer | 4.5/5 | Evaluated | ❌ | [87](reports/087-acme-2026-08-18.md) | |
# after — different requisitions: disambiguate both rows with req ids in notes
| 42 | 2026-08-01 | Acme | Data Engineer | 4.2/5 | Applied | ❌ | [42](reports/042-acme-2026-08-01.md) | req JR-10423 |
| 87 | 2026-08-18 | Acme | Data Engineer | 4.5/5 | Evaluated | ❌ | [87](reports/087-acme-2026-08-18.md) | req JR-10987 |
Defensive patterns

Strategy: type-guard

Validate before calling

const ADVANCED = new Set(['Applied', 'Responded', 'Interview', 'Offer', 'Hired']);
const rowsSafeToAutoMerge = (a, b) => !ADVANCED.has(a.status) && !ADVANCED.has(b.status);

Type guard

const isAdvancedStatus = (s) =>
  ['Applied', 'Responded', 'Interview', 'Offer', 'Hired'].includes(String(s ?? '').trim());

Prevention

When it happens

Trigger: The same company+role appears twice — e.g. a re-scan re-evaluated a posting you already applied to, or a backfilled row sits beside an original now in Interview — and node dedup-tracker.mjs runs over them.

Common situations: Re-scans after applying; reposted requisitions; leveled variants sharing one title; periodic tracker-hygiene sessions.

Related errors


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