santifer/career-ops · error
Cross-channel duplicate — ${key.replace('::', ' / ')} reache
Error message
Cross-channel duplicate — ${key.replace('::', ' / ')} reached via ${list.map(v => v.raw).join(' AND ')} (rows ${list.map(v => `#${v.num}`).join(', ')}) — double-submission risk, resolve by hand What it means
Error "Cross-channel duplicate — ${key.replace('::', ' / ')} reached via ${list.map(v => v.raw).join(' AND ')} (rows ${list.map(v => `#${v.num}`).join(', ')}) — double-submission risk, resolve by hand" thrown in santifer/career-ops.
Source
Thrown at verify-pipeline.mjs:407
// and dedup-tracker key agencies with (#2397), so "Hays" and "HAYS " read as one
// channel while リクルート and パーソル stay two; the raw spelling is kept for
// the message. Before this, both non-Latin agencies normalized to '' and fell
// back to 'direct', hiding exactly the double-submission this check exists for.
const normalizeChannel = (v) => normalizeVia(v ?? '') || 'direct';
const channelsByRole = new Map();
for (const e of entries) {
const company = String(e.company || '').trim();
if (!company || company === '?') continue;
const key = `${company.toLowerCase()}::${String(e.role || '').trim().toLowerCase()}`;
if (!channelsByRole.has(key)) channelsByRole.set(key, new Map());
const channels = channelsByRole.get(key);
const norm = normalizeChannel(e.via);
if (!channels.has(norm)) channels.set(norm, { raw: String(e.via || '').trim() || '—', num: e.num });
}
for (const [key, vias] of channelsByRole) {
if (vias.size > 1) {
const list = [...vias.values()];
warn(`Cross-channel duplicate — ${key.replace('::', ' / ')} reached via ${list.map(v => v.raw).join(' AND ')} (rows ${list.map(v => `#${v.num}`).join(', ')}) — double-submission risk, resolve by hand`);
viaIssues++;
}
}
if (viaIssues === 0) ok('Via channels consistent');
// --- Check 12: Duplicate tracker numbers (#1704) ---
// The # column is a row id and must be unique. Unlike Check 2 (company+role
// dedup, which can false-positive on a legitimate re-application), the SAME
// number appearing on 2+ rows is never legitimate: it means set-status.mjs
// can't tell the rows apart, and any external reference to "application #N"
// (interview-prep notes, memory, cross-links) becomes ambiguous. Pure
// addition, no existing check covers this — see #1704 for the 124-row sweep
// that found this in the wild (merge-tracker.mjs trusted a stale TSV number
// as-is whenever it exceeded that run's max, without checking it wasn't
// already used by an unrelated row merged in a separate, earlier invocation).
const numGroups = new Map();
for (const e of entries) {
if (!numGroups.has(e.num)) numGroups.set(e.num, []);
numGroups.get(e.num).push(e);View on GitHub (pinned to 60398d6549)
When it happens
Trigger: Thrown at verify-pipeline.mjs:407 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/fbbbff2d37e21019.
Report an issue: GitHub.