{"record":{"id":"0aa5e8c4f590c350","repo":"affaan-m/ECC","slug":"recorded-at-must-be-an-iso-timestamp","errorCode":null,"errorMessage":"recorded_at must be an ISO timestamp","messagePattern":"recorded_at must be an ISO timestamp","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/skill-evolution/tracker.js","lineNumber":78,"sourceCode":"\n  if (typeof skillVersion !== 'string' || skillVersion.trim().length === 0) {\n    throw new Error('skill_version is required');\n  }\n\n  if (typeof taskDescription !== 'string' || taskDescription.trim().length === 0) {\n    throw new Error('task_description is required');\n  }\n\n  if (!VALID_OUTCOMES.has(outcome)) {\n    throw new Error('outcome must be one of success, failure, or partial');\n  }\n\n  if (userFeedback !== null && !VALID_FEEDBACK.has(userFeedback)) {\n    throw new Error('user_feedback must be accepted, corrected, rejected, or null');\n  }\n\n  if (Number.isNaN(Date.parse(recordedAt))) {\n    throw new Error('recorded_at must be an ISO timestamp');\n  }\n\n  return {\n    skill_id: skillId,\n    skill_version: skillVersion,\n    task_description: taskDescription,\n    outcome,\n    failure_reason: input.failure_reason || input.failureReason || null,\n    tokens_used: toNullableNumber(input.tokens_used ?? input.tokensUsed, 'tokens_used'),\n    duration_ms: toNullableNumber(input.duration_ms ?? input.durationMs, 'duration_ms'),\n    user_feedback: userFeedback,\n    recorded_at: recordedAt,\n  };\n}\n\nfunction readJsonl(filePath) {\n  if (!fs.existsSync(filePath)) {\n    return [];","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/skill-evolution/tracker.js#L60-L96","documentation":"Thrown by normalizeExecutionRecord when recorded_at fails Date.parse. The value is resolved as input.recorded_at, else options.now, else new Date().toISOString() (always valid). So this only fires when an explicit recorded_at (or options.now) is unparseable.","triggerScenarios":"recordSkillRun({ ..., recorded_at: 'today' }); recorded_at: '2025/12/31' in a format the engine rejects; options.now set to a bad string and no input.recorded_at to override it.","commonSituations":"Forwarding a locale date; passing a unix number where a string is expected (Date.parse(number) is engine-dependent); timezone tokens that break parsing.","solutions":["Pass recorded_at as an ISO 8601 string, or omit it to use options.now / the current time.","Pre-validate with Number.isNaN(Date.parse(value)).","Normalize via new Date(value).toISOString()."],"exampleFix":"// before\nrecordSkillRun({ ..., recorded_at: '12/31/2025 5pm' });\n\n// after\nrecordSkillRun({ ..., recorded_at: new Date('12/31/2025 5pm').toISOString() });","handlingStrategy":"validation","validationCode":"function resolveRecordedAt(input, opts) {\n  const v = input.recorded_at || opts.now || new Date().toISOString();\n  if (Number.isNaN(Date.parse(v))) throw new TypeError('recorded_at not ISO');\n  return v;\n}","typeGuard":"function isIsoTimestamp(v) {\n  return typeof v === 'string' && !Number.isNaN(Date.parse(v));\n}","tryCatchPattern":"try {\n  recordSkillRun(input);\n} catch (err) {\n  if (/recorded_at must be an ISO timestamp/.test(err.message)) { input.recorded_at = new Date().toISOString(); recordSkillRun(input); }\n  else throw err;\n}","preventionTips":["Generate recorded_at with new Date().toISOString() at capture time.","Let the recorder default it rather than passing a value.","Validate timestamps at ingest for replayed/imported data."],"tags":["tracker","timestamp","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}