{"record":{"id":"f6a08214d441b472","repo":"affaan-m/ECC","slug":"invalid-now-timestamp-now-f6a082","errorCode":null,"errorMessage":"Invalid now timestamp: ${now}","messagePattern":"Invalid now timestamp: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/skill-evolution/health.js","lineNumber":149,"sourceCode":"  if (records.length === 0) {\n    return null;\n  }\n\n  return records\n    .map(record => ({\n      timestamp: record.recorded_at,\n      timeMs: Date.parse(record.recorded_at),\n    }))\n    .filter(entry => !Number.isNaN(entry.timeMs))\n    .sort((left, right) => left.timeMs - right.timeMs)\n    .at(-1)?.timestamp || null;\n}\n\nfunction collectSkillHealth(options = {}) {\n  const now = options.now || new Date().toISOString();\n  const nowMs = Date.parse(now);\n  if (Number.isNaN(nowMs)) {\n    throw new Error(`Invalid now timestamp: ${now}`);\n  }\n\n  const warnThreshold = typeof options.warnThreshold === 'number'\n    ? options.warnThreshold\n    : Number(options.warnThreshold || 0.1);\n  if (!Number.isFinite(warnThreshold) || warnThreshold < 0) {\n    throw new Error(`Invalid warn threshold: ${options.warnThreshold}`);\n  }\n\n  const records = tracker.readSkillExecutionRecords(options);\n  const skillsById = discoverSkills(options);\n  const recordsBySkill = records.reduce((groupedRecords, record) => {\n    if (!groupedRecords.has(record.skill_id)) {\n      groupedRecords.set(record.skill_id, []);\n    }\n\n    groupedRecords.get(record.skill_id).push(record);\n    return groupedRecords;","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/skill-evolution/health.js#L131-L167","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass an ISO 8601 string: collectSkillHealth({ now: new Date().toISOString() }).","Validate before calling: Number.isNaN(Date.parse(value)) check in the caller with a clear message.","Convert Date objects to strings first: now: dateObj.toISOString().","Omit `now` so the function defaults to the current time."],"exampleFix":"// before\nhealth.collectSkillHealth({ now: new Date() }); // Date object\n\n// after\nhealth.collectSkillHealth({ now: new Date().toISOString() });","handlingStrategy":"validation","validationCode":"function assertIsoNow(now) {\n  if (now !== undefined && (typeof now !== 'string' || Number.isNaN(Date.parse(now)))) {\n    throw new Error(`now must be an ISO date string, got: ${now}`);\n  }\n}","typeGuard":"const isIsoTimestamp = (v) => v === undefined || (typeof v === 'string' && !Number.isNaN(Date.parse(v)));","tryCatchPattern":"try {\n  report = health.collectSkillHealth(opts);\n} catch (err) {\n  if (err.message.startsWith('Invalid now timestamp')) {\n    delete opts.now;\n    report = health.collectSkillHealth(opts);\n  } else throw err;\n}","preventionTips":["Convert any Date objects to .toISOString() strings before passing as options.now.","Validate timestamps in test fixtures with Date.parse in a beforeAll hook.","Keep one canonical timestamp format across config, env, and code."],"tags":["validation","timestamp","skill-health"],"backgroundTag":"invalid-date-format","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}