affaan-m/ECC · error
${fieldName} must be a number
Error message
${fieldName} must be a number What it means
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.
Source
Thrown at scripts/lib/skill-evolution/tracker.js:39
return homeDir ? path.resolve(homeDir) : os.homedir();
}
function getRunsFilePath(options = {}) {
if (options.runsFilePath) {
return path.resolve(options.runsFilePath);
}
return path.join(resolveHomeDir(options.homeDir), '.claude', 'state', 'skill-runs.jsonl');
}
function toNullableNumber(value, fieldName) {
if (value === null || typeof value === 'undefined') {
return null;
}
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
throw new Error(`${fieldName} must be a number`);
}
return numericValue;
}
function normalizeExecutionRecord(input, options = {}) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new Error('skill execution payload must be an object');
}
const skillId = input.skill_id || input.skillId;
const skillVersion = input.skill_version || input.skillVersion;
const taskDescription = input.task_description || input.task_attempted || input.taskAttempted;
const outcome = input.outcome;
const recordedAt = input.recorded_at || options.now || new Date().toISOString();
const userFeedback = input.user_feedback || input.userFeedback || null;
if (typeof skillId !== 'string' || skillId.trim().length === 0) {View on GitHub (pinned to 01e15490f0)
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.
Example fix
// before
recordSkillRun({ ..., tokens_used: 'n/a' });
// after
recordSkillRun({ ..., tokens_used: Number.isFinite(tokens) ? tokens : null }); Defensive patterns
Strategy: validation
Validate before calling
function sanitizeNumber(v) {
if (v == null) return null;
const n = Number(v);
return Number.isFinite(n) ? n : null;
}
recordSkillRun({ ..., tokens_used: sanitizeNumber(raw.tokens), duration_ms: sanitizeNumber(raw.duration) }); Type guard
function isNullableNumber(v) {
return v == null || (typeof v === 'number' && Number.isFinite(v));
} Try / catch
try {
normalizeExecutionRecord(input);
} catch (err) {
if (/must be a number/.test(err.message)) {
input.tokens_used = null; input.duration_ms = null;
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: Telemetry field sent with a descriptive string instead of a count; a division producing Infinity; NaN propagation from a failed parse upstream.
Related errors
- skill execution payload must be an object
- skill_id is required
- skill_version is required
- task_description is required
- outcome must be one of success, failure, or partial
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/cfdb238a90af6722.
Report an issue: GitHub.