santifer/career-ops · error · SeedError
INVALID_DATE
INVALID_DATE
Error message
Application #${row?.num ?? '?'} notes carry an impossible "Applied ${notesDate}" date; fix the notes or pass --date What it means
resolveAppliedDate derives an application's Applied date for seeding the first follow-up: from an explicit --date, else from an 'Applied YYYY-MM-DD' pattern in the tracker row's notes, else from the row's date cell. When the notes contain an Applied date string that parses but is not a valid calendar date (e.g. 2026-02-30, month 13), it throws a SeedError with code INVALID_DATE rather than pinning a nonsense follow-up date.
Solutions
- Fix the notes column of the named application row so the 'Applied <date>' value is a real calendar date, then re-run.
- Pass an explicit --date YYYY-MM-DD to override the notes value for this run.
- If the date cell (not the notes) is the intended source, remove the bogus 'Applied ...' text from notes so resolution falls through to the date cell.
- Update via node set-status.mjs <row> <State> --note ... to rewrite notes through the canonical path instead of hand-editing the table.
- Add a date sanity check to any script that writes 'Applied' notes to prevent recurrence.
Example fix
// before: tracker row notes | 42 | 2025-09-01 | Acme | Backend | — | Applied | ❌ | [42](reports/042-acme.md) | Applied 2025-02-30, emailed recruiter | // // after: corrected notes (2025-02-28) | 42 | 2025-09-01 | Acme | Backend | — | Applied | ❌ | [42](reports/042-acme.md) | Applied 2025-02-28, emailed recruiter |
Defensive patterns
Strategy: validation
Validate before calling
function isRealCalendarDate(s) {
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s ?? '');
if (!m) return false;
const d = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
return d.getUTCFullYear() === +m[1] && d.getUTCMonth() === +m[2] - 1 && d.getUTCDate() === +m[3];
}
// check notes for /Applied (\d{4}-\d{2}-\d{2})/ and run isRealCalendarDate before seeding Try / catch
try {
const { appliedDate } = resolveAppliedDate(row, cliDate);
} catch (err) {
if (err.code === 'INVALID_DATE') {
console.error(`${err.message}\nFix the row's notes or pass --date YYYY-MM-DD`);
process.exit(1);
}
throw err;
} Prevention
- Verify Applied dates in notes with a calendar check (Feb 30 / month 13 are the classic slips)
- Use node set-status.mjs --note to rewrite notes instead of hand-editing the tracker table
- Pass an explicit --date when the notes date is suspect rather than letting resolution guess
- Standardize on ISO YYYY-MM-DD everywhere to avoid day/month swap errors
- Add a tracker lint that flags date-like strings in notes failing isValidCalendarDate
When it happens
Trigger: Running followup-seed (or a mode that seeds follow-ups) without --date on a tracker row whose notes contain something like 'Applied 2025-02-30' or 'Applied 2025-13-01' — a syntactically date-like but calendar-impossible value that parseAppliedDate extracts and isValidCalendarDate rejects.
Common situations: Typo when hand-writing notes ('Applied 2025-04-31'); swapped day/month after converting from US format (2025-04-13 vs 13th month); row duplicated with an edited but broken date; notes written by a script with an unvalidated computed date.
Related errors
- Cross-channel duplicate
- # : company " " looks like a confidentiality placeholder —…
- # : Score has markdown bold
- ⚠️ Keep # and # : exact-title match but advanced status…
- ⚠️ Non-canonical status
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/35d7fe68aada8eeb.
Report an issue: GitHub.
Appendix: source
Thrown at followup-seed.mjs:177
* `today` remains the last resort for a row whose `date` column is missing or
* unusable — but it is now labelled too, rather than being indistinguishable
* from a measured date.
*
* A notes date that isn't a real calendar date (e.g. "Applied 2026-02-31")
* throws rather than falling through: `parseDate` would return null for it and
* the pin would be written with a literal "null" next-date.
*
* @param {{num?: number, notes?: string, date?: string}} row - Parsed tracker row.
* @param {string|null|undefined} explicitDate - `--date` value, already validated.
* @returns {{appliedDate: string, appDateSource: 'explicit'|'notes'|'evaluation-date'|'today'}}
* @throws {SeedError} INVALID_DATE when the notes carry an impossible date.
*/
export function resolveAppliedDate(row, explicitDate) {
if (explicitDate) return { appliedDate: explicitDate, appDateSource: 'explicit' };
const notesDate = parseAppliedDate(row?.notes);
if (notesDate) {
if (!isValidCalendarDate(notesDate)) {
throw new SeedError('INVALID_DATE', `Application #${row?.num ?? '?'} notes carry an impossible "Applied ${notesDate}" date; fix the notes or pass --date`);
}
return { appliedDate: notesDate, appDateSource: 'notes' };
}
// Only a usable calendar date qualifies: a blank or malformed `date` cell
// must not become a pin with a "null" next-date, which is the same failure
// the notes branch throws over.
const columnDate = String(row?.date ?? '').trim();
if (isValidCalendarDate(columnDate)) {
return { appliedDate: columnDate, appDateSource: 'evaluation-date' };
}
return { appliedDate: todayStr(), appDateSource: 'today' };
}
/**
* Format one pin directive line. The parser side lives in
* followup-cadence.mjs's `OVERRIDE_RE` / `parseNextOverrides`.
*
* @param {number} appNumView on GitHub (pinned to aac998c7ed)