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
- Check the row's status in data/applications.md — only seed follow-ups for rows actually in Applied state
- If seeding is intentional despite the non-Applied status, re-run with --force (CLI) or options.force: true (API)
- If the row should be Applied, fix the tracker status first via `node set-status.mjs <appNum> Applied` and then re-run the seed
- 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
- Only run follow-up seeding on rows that are genuinely in the Applied state
- Fix tracker status first with `node set-status.mjs <appNum> Applied` rather than reaching for --force by default
- Treat --force as a deliberate, logged override — never bake it into automation for all rows
- Filter candidate rows by normalized status 'applied' before iterating seeds
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
- Application Answers section has
- requires a path
- # : — no automatic update
- Conflicting status recommendations require manual review:
- ⚠️ Could not save report
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)