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

  1. Use exactly 'success', 'failure', or 'partial' (lowercase).
  2. Map your internal status vocabulary onto these three before recording.
  3. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/ad97df5595b8e0c7. Report an issue: GitHub.