affaan-m/ECC · error
user_feedback must be accepted, corrected, rejected, or null
Error message
user_feedback must be accepted, corrected, rejected, or null
What it means
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.
Source
Thrown at scripts/lib/skill-evolution/tracker.js:74
if (typeof skillId !== 'string' || skillId.trim().length === 0) {
throw new Error('skill_id is required');
}
if (typeof skillVersion !== 'string' || skillVersion.trim().length === 0) {
throw new Error('skill_version is required');
}
if (typeof taskDescription !== 'string' || taskDescription.trim().length === 0) {
throw new Error('task_description is required');
}
if (!VALID_OUTCOMES.has(outcome)) {
throw new Error('outcome must be one of success, failure, or partial');
}
if (userFeedback !== null && !VALID_FEEDBACK.has(userFeedback)) {
throw new Error('user_feedback must be accepted, corrected, rejected, or null');
}
if (Number.isNaN(Date.parse(recordedAt))) {
throw new Error('recorded_at must be an ISO timestamp');
}
return {
skill_id: skillId,
skill_version: skillVersion,
task_description: taskDescription,
outcome,
failure_reason: input.failure_reason || input.failureReason || null,
tokens_used: toNullableNumber(input.tokens_used ?? input.tokensUsed, 'tokens_used'),
duration_ms: toNullableNumber(input.duration_ms ?? input.durationMs, 'duration_ms'),
user_feedback: userFeedback,
recorded_at: recordedAt,
};
}View on GitHub (pinned to 01e15490f0)
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.
Example fix
// before
recordSkillRun({ ..., user_feedback: 'approved' });
// after
const feedback = { approved: 'accepted', edited: 'corrected', dismissed: 'rejected' }[uiLabel] || null;
recordSkillRun({ ..., user_feedback: feedback }); Defensive patterns
Strategy: validation
Validate before calling
const VALID_FEEDBACK = new Set(['accepted','corrected','rejected']);
function normalizeFeedback(f) {
if (f == null || f === '') return null;
if (!VALID_FEEDBACK.has(f)) throw new TypeError(`user_feedback '${f}' invalid`);
return f;
} Type guard
function isValidFeedback(f) {
return f == null || ['accepted','corrected','rejected'].includes(f);
} Try / catch
try {
recordSkillRun(input);
} catch (err) {
if (/user_feedback must be/.test(err.message)) { input.user_feedback = null; recordSkillRun(input); }
else throw err;
} Prevention
- Translate UI labels to the canonical set before recording.
- Treat absence as null, not an empty string.
- Validate feedback at the form boundary.
When it happens
Trigger: recordSkillRun({ ..., user_feedback: 'yes' }); user_feedback: 'approved'; user_feedback: 'fixed'. Note: user_feedback: '' coerces to null and does NOT throw.
Common situations: Synonyms from a UI (approved/yes/no); uppercase values; forwarding raw button labels instead of canonical tokens.
Related errors
- outcome must be one of success, failure, or partial
- assigneeKind must be 'agent' or 'human'.
- Invalid lane '${lane}'. Expected one of ${[...VALID_LANES].j
- ${label} must be one of: ${allowed.join(', ')}.
- ${fieldName} must be a number
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/b1d58d80a129bd47.
Report an issue: GitHub.