affaan-m/ECC · error
outcome must be one of success, failure, or partial
Error message
outcome must be one of success, failure, or partial
What it means
Thrown by normalizeExecutionRecord when outcome is not in VALID_OUTCOMES = { success, failure, partial }. Unlike other fields, outcome has no alias and no default; it must be exactly one of the three lowercase strings.
Source
Thrown at scripts/lib/skill-evolution/tracker.js:70
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) {
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'),View on GitHub (pinned to 01e15490f0)
Solutions
- Use exactly 'success', 'failure', or 'partial' (lowercase).
- Map your internal status vocabulary onto these three before recording.
- If outcome is unknown, default to 'partial' rather than omitting.
Example fix
// before
recordSkillRun({ ..., outcome: run.ok ? 'succeeded' : 'failed' });
// after
const outcome = run.ok ? 'success' : 'failure';
recordSkillRun({ ..., outcome }); Defensive patterns
Strategy: validation
Validate before calling
const VALID_OUTCOMES = new Set(['success','failure','partial']);
function normalizeOutcome(o) {
if (!VALID_OUTCOMES.has(o)) throw new TypeError(`outcome '${o}' invalid`);
return o;
} Type guard
function isValidOutcome(o) {
return ['success','failure','partial'].includes(o);
} Try / catch
try {
recordSkillRun(input);
} catch (err) {
if (/outcome must be one of/.test(err.message)) { input.outcome = 'partial'; recordSkillRun(input); }
else throw err;
} Prevention
- Map internal statuses to the three canonical outcomes at the boundary.
- Default uncertain results to 'partial'.
- Add a unit test asserting every internal status maps to a valid outcome.
When it happens
Trigger: recordSkillRun({ ..., outcome: 'succeeded' }); outcome: 'ok'; outcome: 'SUCCESS' (case-sensitive); outcome omitted (undefined not in set); outcome: 'error'.
Common situations: Using a synonym (succeeded, completed, failed, errored); uppercase from a config; forgetting to set outcome in the recording path.
Related errors
- user_feedback must be accepted, corrected, rejected, or null
- 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/ad97df5595b8e0c7.
Report an issue: GitHub.