affaan-m/ECC · error · Error

Canonical session snapshot requires workers[${index}].artifa

Error message

Canonical session snapshot requires workers[${index}].artifacts to be an object

What it means

worker.artifacts is missing or not a plain object. The schema only requires artifacts to be an object; its inner keys are adapter-specific (e.g. dmux uses statusFile/taskFile/handoffFile, claude-history uses sessionFile/context). This is the last per-worker guard before the loop closes.

Source

Thrown at scripts/lib/session-adapters/canonical-session.js:231

    ensureBoolean(worker.runtime.dead, `workers[${index}].runtime.dead`);

    if (!isObject(worker.intent)) {
      throw new Error(`Canonical session snapshot requires workers[${index}].intent to be an object`);
    }

    ensureStringAllowEmpty(worker.intent.objective, `workers[${index}].intent.objective`);
    ensureArrayOfStrings(worker.intent.seedPaths, `workers[${index}].intent.seedPaths`);

    if (!isObject(worker.outputs)) {
      throw new Error(`Canonical session snapshot requires workers[${index}].outputs to be an object`);
    }

    ensureArrayOfStrings(worker.outputs.summary, `workers[${index}].outputs.summary`);
    ensureArrayOfStrings(worker.outputs.validation, `workers[${index}].outputs.validation`);
    ensureArrayOfStrings(worker.outputs.remainingRisks, `workers[${index}].outputs.remainingRisks`);

    if (!isObject(worker.artifacts)) {
      throw new Error(`Canonical session snapshot requires workers[${index}].artifacts to be an object`);
    }
  });

  if (!isObject(snapshot.aggregates)) {
    throw new Error('Canonical session snapshot requires aggregates to be an object');
  }

  ensureInteger(snapshot.aggregates.workerCount, 'aggregates.workerCount');
  if (snapshot.aggregates.workerCount !== snapshot.workers.length) {
    throw new Error('Canonical session snapshot requires aggregates.workerCount to match workers.length');
  }

  if (!isObject(snapshot.aggregates.states)) {
    throw new Error('Canonical session snapshot requires aggregates.states to be an object');
  }

  if (!isObject(snapshot.aggregates.healths)) {
    throw new Error('Canonical session snapshot requires aggregates.healths to be an object');

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Always emit artifacts as an object, even if empty: artifacts: {} or artifacts: { sessionFile: null }.
  2. Follow the established adapters: dmux maps worker.files.*, claude-history maps sessionFile:session.sessionPath.
  3. When no artifacts exist, use artifacts: {} rather than omitting or nulling the field.
  4. Pin a unit test that validates the full normalize* output for each adapter.

Example fix

// before
const worker = { id:'w1', label:'w1', state:'recorded', health:'healthy', runtime:{...}, intent:{...}, outputs:{...} };

// after
const worker = {
  id:'w1', label:'w1', state:'recorded', health:'healthy', runtime:{...}, intent:{...}, outputs:{...},
  artifacts: { sessionFile: session.sessionPath, context: metadata.context || null }
};
Defensive patterns

Strategy: type-guard

Validate before calling

function ensureWorkerArtifacts(worker) {
  if (!worker.artifacts || typeof worker.artifacts !== 'object') {
    worker.artifacts = {};
  }
  return worker;
}

Type guard

function hasValidArtifacts(w) {
  return w.artifacts !== null && typeof w.artifacts === 'object' && !Array.isArray(w.artifacts);
}

Try / catch

try { validateCanonicalSnapshot(snapshot); }
catch (err) {
  if (err.message.includes('.artifacts to be an object')) {
    snapshot.workers.forEach(w => { if (!hasValidArtifacts(w)) ensureWorkerArtifacts(w); });
    validateCanonicalSnapshot(snapshot);
  } else throw err;
}

Prevention

When it happens

Trigger: Custom adapter omits the artifacts block entirely. Persisted recording edited to delete artifacts. Adapter sets artifacts to null when no files are present.

Common situations: New adapter for a session source that exposes no on-disk artifacts, where the author skips the field instead of emitting an empty object. Test fixtures that build a minimal worker.

Related errors


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