santifer/career-ops · warning

Skipped #${r.num}: status changed from ${r.oldStatus} to ${r

Error message

Skipped #${r.num}: status changed from ${r.oldStatus} to ${result.conflicts.get(r.num)} during review

What it means

Optimistic-concurrency guard in reply-watch's apply step: after the user answers 'y', updateTrackerStatuses() re-reads the tracker and only writes rows whose current status still equals the oldStatus captured during classification. A row that changed between review and apply (another terminal, a set-status.mjs or merge-tracker run) lands in result.conflicts and is skipped with this warning instead of clobbering the newer state.

Source

Thrown at reply-watch.mjs:319

    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 result = await updateTrackerStatuses(updates);
      for (const r of updates) {
        const count = r.count > 1 ? ` (${r.count} replies)` : '';
        if (result.applied.has(r.num)) {
          console.log(`Updated #${r.num} to ${r.newStatus}${count}`);
        } else if (result.alreadyCurrent.has(r.num)) {
          console.log(`No change for #${r.num}: already ${r.newStatus}${count}`);
        } else if (result.conflicts.has(r.num)) {
          console.warn(`Skipped #${r.num}: status changed from ${r.oldStatus} to ${result.conflicts.get(r.num)} during review`);
        } else if (result.missing.has(r.num)) {
          console.warn(`Skipped #${r.num}: row no longer exists in the tracker`);
        }
      }
      console.log('\n✅ Tracker review complete');

      // Sync tracker DB if tracker.mjs exists
      try {
        const { execSync } = await import('child_process');
        execSync('node tracker.mjs sync', { stdio: 'ignore' });
        console.log('Synced database index (applications.db).');
      } catch (e) {
        // ignore
      }
    } else {
      console.log('Updates skipped.');
    }
  }

View on GitHub (pinned to 60398d6549)

Solutions

  1. Re-run node reply-watch.mjs -- it re-reads current statuses and re-recommends against the fresh state.
  2. Or set the intended state directly: node set-status.mjs <num> <State> --note '...'.
  3. Avoid concurrent writers: finish reply-watch before running other tracker-mutating commands.

Example fix

# before
Skipped #42: status changed from Responded to Interview during review
# after: re-classify against the fresh tracker
$ node reply-watch.mjs   # now recommends from Interview onward
Defensive patterns

Strategy: retry

Validate before calling

// Minimize the review window: capture tracker state and apply promptly
const snapshot = readTrackerStatuses(); // classify + prompt user quickly
// re-validate right before applying
const current = readTrackerStatuses();
const safe = updates.filter(u => current.get(u.num) === u.oldStatus);

Prevention

When it happens

Trigger: Editing applications.md or running set-status.mjs / merge-tracker.mjs in a second terminal while reply-watch's confirmation prompt is open; long pauses between classification and the y/N answer; scheduled automation firing mid-review.

Common situations: Two sessions open on the same repo; cron-scheduled merges overlapping an interactive reply-watch; review prompts left overnight.

Related errors


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