{"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":"Error","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/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/skill-evolution/tracker.js#L21-L57","documentation":"toNullableNumber() coerces a value to a finite number for fields in a skill execution record (e.g. duration, token counts). null/undefined pass through as null, but any other value that Number() cannot turn into a finite number (NaN, Infinity, non-numeric strings) throws '${fieldName} must be a number'. It is a strict numeric-field validator used by normalizeExecutionRecord.","triggerScenarios":"Passing a non-numeric value for a numeric field of an execution record, e.g. record({ duration_ms: 'fast' }) or { tokens: Infinity } — any value where Number.isFinite(Number(value)) is false and the value is not null/undefined.","commonSituations":"Storing durations as formatted strings ('1.2s', '120 ms') from logs; JSON payloads where a number was serialized as a string with units; spreadsheet/CSV imports producing text cells; accidentally passing the field object instead of its numeric value.","solutions":["Pass a plain finite number for the field (e.g. duration_ms: 1200 instead of '1200ms').","Parse/ununit the value before calling: Number(String(v).replace(/[^0-9.]/g, '')) and verify Number.isFinite.","Omit the field (or pass null) if the measurement is genuinely unknown.","Fix upstream serialization so numbers are not stringified with units."],"exampleFix":"// before\nrecord({ skill_id: 'linting', duration_ms: '1.2s' });\n\n// after\nrecord({ skill_id: 'linting', duration_ms: 1200 });","handlingStrategy":"type-guard","validationCode":"function assertFiniteNumber(fieldName, value) {\n  if (value === null || value === undefined) return null;\n  const n = Number(value);\n  if (!Number.isFinite(n)) throw new TypeError(`${fieldName} must be a number, got: ${JSON.stringify(value)}`);\n  return n;\n}\n\nconst durationMs = assertFiniteNumber('duration_ms', raw.duration_ms);","typeGuard":"function isFiniteNumber(value) {\n  return typeof value === 'number' && Number.isFinite(value);\n}","tryCatchPattern":"try {\n  tracker.record(executionPayload);\n} catch (err) {\n  if (err.message.endsWith('must be a number')) {\n    const field = err.message.split(' ')[0];\n    console.error(`Numeric field ${field} is malformed in execution payload`);\n    return;\n  }\n  throw err;\n}","preventionTips":["Store metrics as raw numbers (milliseconds, counts), never formatted strings.","Sanitize values from logs/CSV/JSON imports with Number() + Number.isFinite before recording.","Pass null explicitly for unknown measurements instead of placeholder strings."],"tags":["skill-evolution","validation","numeric-fields"],"backgroundTag":"invalid-argument-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"}