{"record":{"id":"b1d58d80a129bd47","repo":"affaan-m/ECC","slug":"user-feedback-must-be-accepted-corrected-rejecte","errorCode":null,"errorMessage":"user_feedback must be accepted, corrected, rejected, or null","messagePattern":"user_feedback must be accepted, corrected, rejected, or null","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/skill-evolution/tracker.js","lineNumber":74,"sourceCode":"\n  if (typeof skillId !== 'string' || skillId.trim().length === 0) {\n    throw new Error('skill_id is required');\n  }\n\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}","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/skill-evolution/tracker.js#L56-L92","documentation":"Thrown by normalizeExecutionRecord when user_feedback is present but not in VALID_FEEDBACK = { accepted, corrected, rejected }. null is explicitly allowed (no feedback). Because the value is read as `input.user_feedback || input.userFeedback || null`, an empty string '' is falsy and becomes null, so only non-empty invalid strings trigger this.","triggerScenarios":"recordSkillRun({ ..., user_feedback: 'yes' }); user_feedback: 'approved'; user_feedback: 'fixed'. Note: user_feedback: '' coerces to null and does NOT throw.","commonSituations":"Synonyms from a UI (approved/yes/no); uppercase values; forwarding raw button labels instead of canonical tokens.","solutions":["Use 'accepted', 'corrected', 'rejected', or null/omit the field.","Map UI labels to the canonical set before recording.","Pass null explicitly when no feedback was given."],"exampleFix":"// before\nrecordSkillRun({ ..., user_feedback: 'approved' });\n\n// after\nconst feedback = { approved: 'accepted', edited: 'corrected', dismissed: 'rejected' }[uiLabel] || null;\nrecordSkillRun({ ..., user_feedback: feedback });","handlingStrategy":"validation","validationCode":"const VALID_FEEDBACK = new Set(['accepted','corrected','rejected']);\nfunction normalizeFeedback(f) {\n  if (f == null || f === '') return null;\n  if (!VALID_FEEDBACK.has(f)) throw new TypeError(`user_feedback '${f}' invalid`);\n  return f;\n}","typeGuard":"function isValidFeedback(f) {\n  return f == null || ['accepted','corrected','rejected'].includes(f);\n}","tryCatchPattern":"try {\n  recordSkillRun(input);\n} catch (err) {\n  if (/user_feedback must be/.test(err.message)) { input.user_feedback = null; recordSkillRun(input); }\n  else throw err;\n}","preventionTips":["Translate UI labels to the canonical set before recording.","Treat absence as null, not an empty string.","Validate feedback at the form boundary."],"tags":["tracker","enum","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}