{"record":{"id":"239c07da63f8414f","repo":"affaan-m/ECC","slug":"invalid-warn-threshold-options-warnthreshold","errorCode":null,"errorMessage":"Invalid warn threshold: ${options.warnThreshold}","messagePattern":"Invalid warn threshold: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/skill-evolution/health.js","lineNumber":156,"sourceCode":"      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;\n  }, new Map());\n\n  for (const skillId of recordsBySkill.keys()) {\n    if (!skillsById.has(skillId)) {\n      skillsById.set(skillId, {\n        skill_id: skillId,\n        skill_dir: null,","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/skill-evolution/health.js#L138-L174","documentation":"collectSkillHealth validates options.warnThreshold, the fraction (0..1 scale) of stale executions at which a skill is flagged as unhealthy. The value must be a finite number >= 0; strings that Number() cannot convert to a finite non-negative value, NaN, Infinity, and negatives all throw this error.","triggerScenarios":"collectSkillHealth({ warnThreshold: 'high' }); passing warnThreshold: -0.5; passing NaN or Infinity; passing an object/boolean that Number() coerces to NaN (e.g. {} or undefined-like strings where Number('' || 0.1) traps differ from intent).","commonSituations":"CLI --warn-threshold flags passed as raw unparsed strings ('--warn-threshold 0.2' forwarded as ' 0.2 ' with whitespace is fine, but 'twenty percent' is not); config files with percentage strings like '10%' instead of 0.1; copy-pasted negative or typo'd values.","solutions":["Pass a finite non-negative number, e.g. collectSkillHealth({ warnThreshold: 0.1 }).","Parse percentages at the boundary: Number(value.replace('%','')) / 100 before calling.","Guard in the caller: const t = Number(raw); if (!Number.isFinite(t) || t < 0) reject before invoking.","Omit warnThreshold to use the default 0.1."],"exampleFix":"// before\nhealth.collectSkillHealth({ warnThreshold: '10%' });\n\n// after\nconst raw = '10%';\nhealth.collectSkillHealth({ warnThreshold: Number(raw.replace('%', '')) / 100 }); // 0.1","handlingStrategy":"validation","validationCode":"function toWarnThreshold(raw) {\n  const n = typeof raw === 'string' && raw.endsWith('%') ? Number(raw.slice(0, -1)) / 100 : Number(raw);\n  if (!Number.isFinite(n) || n < 0) throw new Error(`warnThreshold must be a finite number >= 0, got: ${raw}`);\n  return n;\n}","typeGuard":"const isValidThreshold = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;","tryCatchPattern":"try {\n  report = health.collectSkillHealth({ warnThreshold: raw });\n} catch (err) {\n  if (err.message.startsWith('Invalid warn threshold')) {\n    console.error(`Bad --warn-threshold '${raw}'; using default 0.1.`);\n    report = health.collectSkillHealth({});\n  } else throw err;\n}","preventionTips":["Parse numeric CLI flags with Number() and reject NaN at the argument boundary.","Store thresholds as fractions (0.1), never percent strings ('10%').","Document the expected range in config schema/comments."],"tags":["validation","configuration","skill-health"],"backgroundTag":"invalid-config-value","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"}