{"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":"exception","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/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/skill-evolution/health.js#L138-L174","documentation":"Thrown by collectSkillHealth when options.warnThreshold, after Number() coercion (default 0.1), is not finite or is negative. warnThreshold is the fraction below which a skill's success-rate decline raises a warning.","triggerScenarios":"Calling collectSkillHealth({ warnThreshold: 'abc' }) (Number('abc') = NaN), { warnThreshold: -0.2 }, or { warnThreshold: Infinity }. A numeric string like '0.1' is accepted because Number('0.1') = 0.1.","commonSituations":"Config typo passing a non-numeric; passing a negative value by mistake; confusing the threshold as a percentage (passing 10 for '10%') — that won't throw but yields wrong warnings; env var defaulting to an empty/non-numeric string.","solutions":["Pass a non-negative finite number (fraction in 0..1, e.g. 0.1 for 10%).","Omit options.warnThreshold to use the built-in default of 0.1.","If reading from config/env, coerce and bounds-check before calling."],"exampleFix":"// before\ncollectSkillHealth({ warnThreshold: process.env.WARN_THRESHOLD });\n// env unset -> '' -> Number('') === 0, ok; but 'high' -> NaN -> throws\n\n// after\nconst wt = Number(process.env.WARN_THRESHOLD || 0.1);\ncollectSkillHealth({ warnThreshold: Number.isFinite(wt) && wt >= 0 ? wt : 0.1 });","handlingStrategy":"validation","validationCode":"function resolveWarnThreshold(raw) {\n  const n = typeof raw === 'number' ? raw : Number(raw);\n  if (!Number.isFinite(n) || n < 0) return 0.1; // safe default\n  return n;\n}\nhealth.collectSkillHealth({ ...opts, warnThreshold: resolveWarnThreshold(opts.warnThreshold) });","typeGuard":"function isValidWarnThreshold(v) {\n  const n = Number(v);\n  return Number.isFinite(n) && n >= 0;\n}","tryCatchPattern":"try {\n  collectSkillHealth({ warnThreshold });\n} catch (err) {\n  if (/Invalid warn threshold/.test(err.message)) collectSkillHealth({ ...opts });\n  else throw err;\n}","preventionTips":["Express warnThreshold as a fraction (0..1), not a percentage.","Coerce env/config values with Number() and bounds-check before passing.","Default to omitting the option (0.1) when unsure."],"tags":["health","threshold","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}