santifer/career-ops · error · SeedError

NOT_APPLIED

NOT_APPLIED

Error message

Application #${appNum} is not Applied (status: "${row.status.trim()}"); use --force to seed anyway

What it means

followup-seed.mjs seeds a first follow-up date into data/follow-ups.md, but only for applications that are in the Applied state. When the tracker row's status, after normalization, is anything other than 'applied', seedFollowup refuses to seed and throws SeedError with code NOT_APPLIED, suggesting --force as an explicit override. This guard prevents follow-up cadence tracking from starting on rows that were never actually applied to (evaluated, rejected, discarded rows).

Solutions

  1. Check the row's status in data/applications.md — only seed follow-ups for rows actually in Applied state
  2. If seeding is intentional despite the non-Applied status, re-run with --force (CLI) or options.force: true (API)
  3. If the row should be Applied, fix the tracker status first via `node set-status.mjs <appNum> Applied` and then re-run the seed
  4. Use options.assumeApplied: true in the API when the caller has already verified the row semantically represents an application

Example fix

// before
await seedFollowup({ appNum: 42 });
// SeedError NOT_APPLIED: Application #42 is not Applied (status: "Evaluated")

// after — verify then force or fix
const row = readTrackerRows(trackerPath).find(r => r.num === 42);
if (normalizeStatus(row.status) !== 'applied') {
  await seedFollowup({ appNum: 42, force: true }); // deliberate override
}
Defensive patterns

Strategy: validation

Validate before calling

import { readTrackerRows } from './tracker-parse.mjs';
import { normalizeStatus } from './normalize-statuses.mjs';
const row = readTrackerRows('data/applications.md').find(r => r.num === appNum);
if (!row) throw new Error(`Application #${appNum} not found`);
if (normalizeStatus(row.status) !== 'applied') {
  // decide: fix the tracker status, or pass force:true deliberately
}

Type guard

const isApplied = (row) => row && typeof row.status === 'string' && normalizeStatus(row.status) === 'applied';

Try / catch

try {
  await seedFollowup({ appNum });
} catch (e) {
  if (e.code === 'NOT_APPLIED') {
    console.warn(`#${appNum} skipped: ${e.message} (status not Applied; use --force to override)`);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling seedFollowup (or the CLI `node followup-seed.mjs <appNum>`) for an application number whose tracker row status normalizes to something other than 'applied' — e.g. 'Evaluated', 'Interview', 'Rejected', 'Discarded' — without passing --force (or options.force / options.assumeApplied when calling the API directly).

Common situations: Typo or stale row number passed on the CLI pointing at an evaluated-but-never-applied row; trying to seed follow-ups for a row backfilled with status 'N/A' or 'Discarded'; a workflow automation that assumes every tracker row is Applied; calling the exported API without setting options.force after an earlier manual status change.

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/2b8f524cf0c425e2. Report an issue: GitHub.

Appendix: source

Thrown at followup-seed.mjs:565

  if (options.date != null && !isValidCalendarDate(options.date)) {
    throw new SeedError('INVALID_DATE', `--date must be a real calendar date in YYYY-MM-DD form: ${options.date}`);
  }

  const trackerPath = resolveTrackerPath(options.trackerPath);
  const followupsPath = resolveFollowupsPath(options.followupsPath);

  if (!existsSync(trackerPath)) {
    throw new SeedError('ROW_NOT_FOUND', `Tracker not found at ${trackerPath}`);
  }
  const rows = readTrackerRows(trackerPath);
  const row = rows.find(r => r.num === appNum);
  if (!row) {
    throw new SeedError('ROW_NOT_FOUND', `Application #${appNum} not found in ${trackerPath}`);
  }

  const normalized = normalizeStatus(row.status);
  if (normalized !== 'applied' && !options.force && !options.assumeApplied) {
    throw new SeedError('NOT_APPLIED', `Application #${appNum} is not Applied (status: "${row.status.trim()}"); use --force to seed anyway`);
  }

  // appDateSource travels with the result so a caller can tell a measured apply
  // date from a proxy or a guess — the same contract followup-cadence.mjs and
  // company-history.mjs already expose.
  const { appliedDate, appDateSource } = resolveAppliedDate(row, options.date);
  const cadence = resolveCadenceConfig({ profilePath: options.profilePath });
  const nextDate = addDays(parseDate(appliedDate), cadence.applied_first);
  const setDate = todayStr();
  const pin = formatPinLine(appNum, nextDate, setDate);

  if (options.dryRun) {
    const existingContent = existsSync(followupsPath) ? readFileSync(followupsPath, 'utf-8') : '';
    if (isAlreadySeeded(existingContent, appNum) && !options.force) {
      return { seeded: false, appNum, pin: null, nextDate, appliedDate, appDateSource, setDate, reason: 'already-seeded', dryRun: true };
    }
    return { seeded: true, appNum, pin, nextDate, appliedDate, appDateSource, setDate, dryRun: true };
  }

View on GitHub (pinned to aac998c7ed)