affaan-m/ECC · error · Error

Invalid now timestamp

Error message

Invalid now timestamp: ${now}

What it means

collectSkillHealth takes an options.now timestamp used as the reference point for staleness and warning computations. The value is parsed with Date.parse; an unparseable value yields NaN which would make every age comparison unreliable, so the function throws before doing any work.

Solutions

  1. Pass an ISO 8601 string: collectSkillHealth({ now: new Date().toISOString() }).
  2. Validate before calling: Number.isNaN(Date.parse(value)) check in the caller with a clear message.
  3. Convert Date objects to strings first: now: dateObj.toISOString().
  4. Omit `now` so the function defaults to the current time.

Example fix

// before
health.collectSkillHealth({ now: new Date() }); // Date object

// after
health.collectSkillHealth({ now: new Date().toISOString() });
Defensive patterns

Strategy: validation

Validate before calling

function assertIsoNow(now) {
  if (now !== undefined && (typeof now !== 'string' || Number.isNaN(Date.parse(now)))) {
    throw new Error(`now must be an ISO date string, got: ${now}`);
  }
}

Type guard

const isIsoTimestamp = (v) => v === undefined || (typeof v === 'string' && !Number.isNaN(Date.parse(v)));

Try / catch

try {
  report = health.collectSkillHealth(opts);
} catch (err) {
  if (err.message.startsWith('Invalid now timestamp')) {
    delete opts.now;
    report = health.collectSkillHealth(opts);
  } else throw err;
}

Prevention

When it happens

Trigger: collectSkillHealth({ now: 'yesterday' }); passing a Date instance instead of a string; passing an empty string ('' parses to NaN via Date.parse); forwarding a raw CLI/env string like '2026-13-45' (invalid month/day) into options.now.

Common situations: Scheduled jobs reading a timestamp from config files with human-formatted dates; tests injecting fixture timestamps with the wrong format; mixing Date objects and strings across callers; locale-dependent date strings produced on a developer machine.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/f6a08214d441b472. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/skill-evolution/health.js:149

  if (records.length === 0) {
    return null;
  }

  return records
    .map(record => ({
      timestamp: record.recorded_at,
      timeMs: Date.parse(record.recorded_at),
    }))
    .filter(entry => !Number.isNaN(entry.timeMs))
    .sort((left, right) => left.timeMs - right.timeMs)
    .at(-1)?.timestamp || null;
}

function collectSkillHealth(options = {}) {
  const now = options.now || new Date().toISOString();
  const nowMs = Date.parse(now);
  if (Number.isNaN(nowMs)) {
    throw new Error(`Invalid now timestamp: ${now}`);
  }

  const warnThreshold = typeof options.warnThreshold === 'number'
    ? options.warnThreshold
    : Number(options.warnThreshold || 0.1);
  if (!Number.isFinite(warnThreshold) || warnThreshold < 0) {
    throw new Error(`Invalid warn threshold: ${options.warnThreshold}`);
  }

  const records = tracker.readSkillExecutionRecords(options);
  const skillsById = discoverSkills(options);
  const recordsBySkill = records.reduce((groupedRecords, record) => {
    if (!groupedRecords.has(record.skill_id)) {
      groupedRecords.set(record.skill_id, []);
    }

    groupedRecords.get(record.skill_id).push(record);
    return groupedRecords;

View on GitHub (pinned to 8321021c54)