santifer/career-ops · info

# : — no automatic update

Error message

  #${conflict.num}: ${summary} — no automatic update

What it means

The per-conflict detail line emitted by reply-watch's main: for tracker row #N it joins each contending status with its reply count (e.g. "Responded (1 reply) vs Interview (2 replies)") and states that no automatic update was made. It exists so the user can arbitrate which status is correct.

Solutions

  1. Read the two (or more) contending statuses and pick the one matching the most recent/authoritative reply, then `node set-status.mjs <#> <State>`.
  2. Inspect the underlying replies in data/reply-candidates.json to spot the misclassified one and correct its classification.
  3. No data is lost — the tracker is untouched — so resolving is purely a manual status decision.

Example fix

// before
//   #18: Responded (1 reply) vs Interview (1 reply)
// after
node set-status.mjs 18 Interview --note "invite received after initial reply"
Defensive patterns

Strategy: validation

Validate before calling

if (conflict.choices.length > 1) {
  console.warn(`#${conflict.num}: ${conflict.choices.map(c => c.newStatus).join(' vs ')} — decide which reply is authoritative`);
}

Try / catch

const summary = conflict.choices
  .map(c => `${c.newStatus} (${c.count} ${c.count === 1 ? 'reply' : 'replies'})`)
  .join(' vs ');
console.warn(`  #${conflict.num}: ${summary} — no automatic update`);

Prevention

When it happens

Trigger: Same trigger as the conflict header: groupStatusRecommendations returns conflicts for a # because classified replies for that row disagree on the target status.

Common situations: Mixed automated + human replies for one application; stale row already advanced past the recommended status; classifier disagreement on ambiguous phrasing ("unfortunately... but keep in touch" vs interview scheduling).

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/de0ef41c11ce5cd4. Report an issue: GitHub.

Appendix: source

Thrown at reply-watch.mjs:299

        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('');

    const answer = await askQuestion(`Apply recommended status updates to ${APPS_FILE}? (y/N): `);
    if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
      const statusLogFile = path.join(path.dirname(APPS_FILE), 'status-log.tsv');
      const todayStr = localToday();

View on GitHub (pinned to aac998c7ed)