{"record":{"id":"cfdb238a90af6722","repo":"affaan-m/ECC","slug":"fieldname-must-be-a-number","errorCode":null,"errorMessage":"${fieldName} must be a number","messagePattern":"(.+?) must be a number","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/skill-evolution/tracker.js","lineNumber":39,"sourceCode":"  return homeDir ? path.resolve(homeDir) : os.homedir();\n}\n\nfunction getRunsFilePath(options = {}) {\n  if (options.runsFilePath) {\n    return path.resolve(options.runsFilePath);\n  }\n\n  return path.join(resolveHomeDir(options.homeDir), '.claude', 'state', 'skill-runs.jsonl');\n}\n\nfunction toNullableNumber(value, fieldName) {\n  if (value === null || typeof value === 'undefined') {\n    return null;\n  }\n\n  const numericValue = Number(value);\n  if (!Number.isFinite(numericValue)) {\n    throw new Error(`${fieldName} must be a number`);\n  }\n\n  return numericValue;\n}\n\nfunction normalizeExecutionRecord(input, options = {}) {\n  if (!input || typeof input !== 'object' || Array.isArray(input)) {\n    throw new Error('skill execution payload must be an object');\n  }\n\n  const skillId = input.skill_id || input.skillId;\n  const skillVersion = input.skill_version || input.skillVersion;\n  const taskDescription = input.task_description || input.task_attempted || input.taskAttempted;\n  const outcome = input.outcome;\n  const recordedAt = input.recorded_at || options.now || new Date().toISOString();\n  const userFeedback = input.user_feedback || input.userFeedback || null;\n\n  if (typeof skillId !== 'string' || skillId.trim().length === 0) {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/skill-evolution/tracker.js#L21-L57","documentation":"Thrown by toNullableNumber (used for tokens_used and duration_ms in normalizeExecutionRecord) when the value is present (not null/undefined) but Number(value) is not finite. null and undefined are allowed and pass through as null.","triggerScenarios":"recordSkillRun({ ..., tokens_used: 'lots' }) (Number('lots')=NaN); duration_ms: true is actually allowed (Number(true)=1); tokens_used: Infinity or 'Infinity' (the string 'Infinity' parses to Infinity which is not finite); tokens_used: NaN. Note numeric strings like '100' are accepted (Number('100')=100).","commonSituations":"Telemetry field sent with a descriptive string instead of a count; a division producing Infinity; NaN propagation from a failed parse upstream.","solutions":["Send tokens_used and duration_ms as finite numbers, or omit them / set null.","Numeric strings are tolerated, but prefer real numbers.","Sanitize Infinity/NaN to null before recording."],"exampleFix":"// before\nrecordSkillRun({ ..., tokens_used: 'n/a' });\n\n// after\nrecordSkillRun({ ..., tokens_used: Number.isFinite(tokens) ? tokens : null });","handlingStrategy":"validation","validationCode":"function sanitizeNumber(v) {\n  if (v == null) return null;\n  const n = Number(v);\n  return Number.isFinite(n) ? n : null;\n}\nrecordSkillRun({ ..., tokens_used: sanitizeNumber(raw.tokens), duration_ms: sanitizeNumber(raw.duration) });","typeGuard":"function isNullableNumber(v) {\n  return v == null || (typeof v === 'number' && Number.isFinite(v));\n}","tryCatchPattern":"try {\n  normalizeExecutionRecord(input);\n} catch (err) {\n  if (/must be a number/.test(err.message)) {\n    input.tokens_used = null; input.duration_ms = null;\n  } else throw err;\n}","preventionTips":["Coerce telemetry to numbers at the source (instrumentation), not at the recorder.","Collapse Infinity/NaN to null before recording.","Document tokens_used/duration_ms as nullable numbers in your payload schema."],"tags":["tracker","telemetry","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}