affaan-m/ECC · error · Error

Invalid now timestamp: ${now}

Error message

Invalid now timestamp: ${now}

What it means

Thrown by collectSkillHealth (the health module that powers the dashboard) when options.now is provided but Date.parse(now) is NaN. Identical guard to dashboard.renderDashboard because health is its upstream and threads `now` through every aggregation. Also reachable via summarizeHealthReport/calculateSuccessRate paths that accept options.now.

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 01e15490f0)

Solutions

  1. Omit options.now or pass a strict ISO 8601 timestamp.
  2. Pre-validate: if (Number.isNaN(Date.parse(now))) throw/fallback before calling.
  3. Normalize user input through new Date(input).toISOString().

Example fix

// before
health.collectSkillHealth({ now: rawTimestamp });

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

Strategy: validation

Validate before calling

const now = opts.now && !Number.isNaN(Date.parse(opts.now)) ? opts.now : new Date().toISOString();
health.collectSkillHealth({ ...opts, now });

Type guard

function isIsoTimestamp(v) {
  return typeof v === 'string' && !Number.isNaN(Date.parse(v));
}

Try / catch

try {
  health.collectSkillHealth({ now });
} catch (err) {
  if (/Invalid now timestamp/.test(err.message)) health.collectSkillHealth({ ...opts });
  else throw err;
}

Prevention

When it happens

Trigger: Calling health.collectSkillHealth({ now: 'not-a-date' }), or any caller (dashboard, CLI) forwarding an invalid now into the health pipeline. A non-empty invalid string is the trigger; falsy values fall back to new Date().toISOString().

Common situations: Same as the dashboard variant: forwarding unvalidated user/clock input; locale-formatted dates; passing a Date object instead of an ISO string.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/f6a08214d441b472. Report an issue: GitHub.