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
- Omit options.now or pass a strict ISO 8601 timestamp.
- Pre-validate: if (Number.isNaN(Date.parse(now))) throw/fallback before calling.
- 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
- Centralize timestamp normalization in one helper reused by dashboard and health callers.
- Never forward raw user input as `now`.
- In tests, generate now from new Date().toISOString().
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
- ${label} must be an ISO-8601 timestamp.
- Invalid now timestamp: ${now}
- Invalid warn threshold: ${options.warnThreshold}
- recorded_at must be an ISO timestamp
- ECC_PROJECT_DIR must be a child path within /workspace.
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/f6a08214d441b472.
Report an issue: GitHub.