santifer/career-ops · warning

Conflicting status recommendations require manual review:

Error message

Conflicting status recommendations require manual review:

What it means

reply-watch.mjs classifies application replies into one suggested tracker status per reply, then groups them per application row (groupStatusRecommendations). When a single tracker number receives contradictory suggestions -- e.g. two replies say Rejected while one says Interview -- the tool refuses to auto-apply any of them, prints this header, and defers to manual review instead of picking a winner silently.

Source

Thrown at reply-watch.mjs:290

    console.log('');

    if (match.application_num !== null && classification.suggestedTrackerUpdate !== 'none' && classification.suggestedTrackerUpdate !== 'Needs Review') {
      const app = apps.find(a => a.num === match.application_num);
      if (app && app.status !== classification.suggestedTrackerUpdate) {
        recommendations.push({
          num: app.num,
          company: app.company,
          role: app.role,
          oldStatus: app.status,
          newStatus: classification.suggestedTrackerUpdate
        });
      }
    }
  });

  const groupedRecommendations = groupStatusRecommendations(recommendations);
  if (groupedRecommendations.conflicts.length > 0) {
    console.warn('Conflicting status recommendations require manual review:');
    for (const conflict of groupedRecommendations.conflicts) {
      const summary = conflict.choices
        .map(choice => `${choice.newStatus} (${choice.count} ${choice.count === 1 ? 'reply' : 'replies'})`)
        .join(' vs ');
      console.warn(`  #${conflict.num}: ${summary} — no automatic update`);
    }
    console.log('');
  }

  if (groupedRecommendations.updates.length > 0) {
    const updates = groupedRecommendations.updates;
    console.log('Suggested status updates to apply:');
    updates.forEach(r => {
      const count = r.count > 1 ? ` (${r.count} replies)` : '';
      console.log(`  #${r.num} ${r.company} (${r.role}): ${r.oldStatus} → ${r.newStatus}${count}`);
    });
    console.log('');

View on GitHub (pinned to 60398d6549)

Solutions

  1. Read the per-conflict detail lines (next warning) to see which statuses and reply counts are fighting over each #num.
  2. Open the raw replies for that row, decide the true current state, and apply it: node set-status.mjs <num> <State> --note 'context'.
  3. Re-run reply-watch -- once the tracker reflects reality, the conflicting suggestions collapse.

Example fix

# before
Conflicting status recommendations require manual review:
  #42: Interview (1 reply) vs Rejected (2 replies) — no automatic update
# after: resolve by hand from the actual emails, most recent wins
$ node set-status.mjs 42 Rejected --note 'rejection email 2026-08-19 confirmed'
Defensive patterns

Strategy: validation

Validate before calling

// Pre-group replies per tracker row and surface disagreements before classification
function findConflicts(repliesByNum) {
  return [...repliesByNum].filter(([, rs]) =>
    new Set(rs.map(r => r.suggestedStatus)).size > 1
  ).map(([num]) => num);
}

Prevention

When it happens

Trigger: Batch-classifying a mailbox where a company sent both a rejection and a later interview-reschedule; auto-reply noise mixing with human replies; threading ambiguity mapping two different roles onto one tracker row.

Common situations: Processing a week of accumulated mail in one run; companies that reject one application then invite for another; multiple applications to the same company.

Related errors


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