affaan-m/ECC · error · Error

skill execution payload must be an object

Error message

skill execution payload must be an object

What it means

normalizeExecutionRecord() validates the payload handed to record() before persisting a skill execution entry. The payload must be a plain object: null, undefined, arrays, and primitives are all rejected with 'skill execution payload must be an object'. This fails fast so downstream field extraction (skill_id, skill_version, etc.) operates on real object input.

Solutions

  1. Pass a single execution-record object to record(), e.g. record({ skill_id, skill_version, task_description, outcome }).
  2. If you have multiple records, call record() once per object in a loop/map instead of passing an array.
  3. Check the call site for a missing argument or failed JSON.parse returning undefined.
  4. Guard with a type check before calling: if (payload && !Array.isArray(payload)) record(payload).

Example fix

// before
tracker.record(JSON.parse(batchFile)); // array of records

// after
for (const rec of JSON.parse(batchFile)) {
  tracker.record(rec);
}
Defensive patterns

Strategy: type-guard

Validate before calling

function isRecordPayload(input) {
  return input !== null && typeof input === 'object' && !Array.isArray(input);
}

if (!isRecordPayload(payload)) {
  throw new TypeError('record() expects a single execution object, not an array or primitive');
}
tracker.record(payload);

Type guard

function isPlainObject(value) {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

Try / catch

try {
  tracker.record(payload);
} catch (err) {
  if (err.message === 'skill execution payload must be an object') {
    console.error('record() received non-object payload; check for missing arg or array batch');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling record(null), record(undefined), record([...] ) with an array of records, or record('skill run') — any input where !input || typeof input !== 'object' || Array.isArray(input) is true.

Common situations: Passing an array of execution records to record() instead of looping; spreading arguments so the payload becomes undefined; a build step that dropped the object argument; JSON.parse failures silently yielding undefined then passed through.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/58ddc9462a3a6eb0. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/skill-evolution/tracker.js:47

  return path.join(resolveHomeDir(options.homeDir), '.claude', 'state', 'skill-runs.jsonl');
}

function toNullableNumber(value, fieldName) {
  if (value === null || typeof value === 'undefined') {
    return null;
  }

  const numericValue = Number(value);
  if (!Number.isFinite(numericValue)) {
    throw new Error(`${fieldName} must be a number`);
  }

  return numericValue;
}

function normalizeExecutionRecord(input, options = {}) {
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
    throw new Error('skill execution payload must be an object');
  }

  const skillId = input.skill_id || input.skillId;
  const skillVersion = input.skill_version || input.skillVersion;
  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) {

View on GitHub (pinned to 8321021c54)