affaan-m/ECC · error
skill execution payload must be an object
Error message
skill execution payload must be an object
What it means
Thrown first in normalizeExecutionRecord when the input is not a plain object: null, undefined, a primitive, or an array all fail. This is the entry guard before any field is read.
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 01e15490f0)
Solutions
- Pass a single parsed plain object.
- If data may be a string, JSON.parse first and assert typeof === 'object' && !Array.isArray.
- Loop over arrays and record one at a time.
Example fix
// before recordSkillRun(rawJsonString); // after const parsed = JSON.parse(rawJsonString); recordSkillRun(parsed);
Defensive patterns
Strategy: type-guard
Validate before calling
function asRecord(input) {
if (typeof input === 'string') input = JSON.parse(input);
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('expected a single record object');
}
return input;
}
recordSkillRun(asRecord(raw)); Type guard
function isPlainObject(v) {
return v != null && typeof v === 'object' && !Array.isArray(v);
} Try / catch
try {
recordSkillRun(input);
} catch (err) {
if (/payload must be an object/.test(err.message)) {
recordSkillRun(JSON.parse(input));
} else throw err;
} Prevention
- Always JSON.parse network/serialized payloads before recording.
- Loop over arrays and record one record per call.
- Assert the parsed shape at the trust boundary.
When it happens
Trigger: recordSkillRun(null); recordSkillRun('{"skill_id":...}') (passing a JSON string instead of parsing it); recordSkillRun([record1, record2]) (passing an array); recordSkillRun(JSON.parse(badData)) where parse yielded a non-object.
Common situations: Forgetting JSON.parse on a serialized payload; forwarding a raw network body string; passing a collection instead of a single record.
Related errors
- ${fieldName} must be a number
- skill_id is required
- skill_version is required
- task_description is required
- outcome must be one of success, failure, or partial
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/58ddc9462a3a6eb0.
Report an issue: GitHub.