affaan-m/ECC · error · Error

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

Error message

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

What it means

worker.outputs is missing or not a plain object. The schema requires outputs to contain three string arrays: summary, validation, remainingRisks. This guard fires before ensureArrayOfStrings on those fields, so any non-object outputs (null, undefined, array) is rejected.

Source

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

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

    ensureString(worker.runtime.kind, `workers[${index}].runtime.kind`);
    ensureOptionalString(worker.runtime.command, `workers[${index}].runtime.command`);
    ensureBoolean(worker.runtime.active, `workers[${index}].runtime.active`);
    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');

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Always emit outputs: { summary: [], validation: [], remainingRisks: [] } at minimum.
  2. For dmux, map from worker.handoff.summary/validation/remainingRisks with Array.isArray fallbacks; for claude-history, summary from metadata.completed, remainingRisks from metadata.notes.
  3. If migrating from an older shape, coerce each field to a string array before validation.
  4. Add a schema-shape unit test for every adapter's normalize* output.

Example fix

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

// after
const worker = {
  id:'w1', label:'w1', state:'recorded', health:'healthy', runtime:{...}, intent:{...},
  outputs: {
    summary: Array.isArray(metadata.completed) ? metadata.completed : [],
    validation: [],
    remainingRisks: metadata.notes ? [metadata.notes] : []
  },
  artifacts:{...}
};
Defensive patterns

Strategy: type-guard

Validate before calling

function ensureWorkerOutputs(worker) {
  if (!worker.outputs || typeof worker.outputs !== 'object') {
    worker.outputs = { summary: [], validation: [], remainingRisks: [] };
  }
  return worker;
}

Type guard

function hasValidOutputs(w) {
  const o = w.outputs;
  if (o === null || typeof o !== 'object' || Array.isArray(o)) return false;
  return ['summary','validation','remainingRisks'].every(k => Array.isArray(o[k]) && o[k].every(x => typeof x === 'string'));
}

Try / catch

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

Prevention

When it happens

Trigger: Custom adapter forgets the outputs block. Persisted recording was hand-edited to remove outputs. Adapter emits outputs as a flat string instead of an object of arrays.

Common situations: Building a new adapter for a session source that has no structured handoff yet and the author omits outputs entirely. Reading a recording written by an experimental branch that used a different outputs shape.

Related errors


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