{"record":{"id":"0826dcbc545992ef","repo":"affaan-m/ECC","slug":"canonical-session-snapshot-requires-workers-inde-0826dc","errorCode":null,"errorMessage":"Canonical session snapshot requires workers[${index}].outputs to be an object","messagePattern":"Canonical session snapshot requires workers\\[(.+?)\\]\\.outputs to be an object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/session-adapters/canonical-session.js","lineNumber":223,"sourceCode":"\n    if (!isObject(worker.runtime)) {\n      throw new Error(`Canonical session snapshot requires workers[${index}].runtime to be an object`);\n    }\n\n    ensureString(worker.runtime.kind, `workers[${index}].runtime.kind`);\n    ensureOptionalString(worker.runtime.command, `workers[${index}].runtime.command`);\n    ensureBoolean(worker.runtime.active, `workers[${index}].runtime.active`);\n    ensureBoolean(worker.runtime.dead, `workers[${index}].runtime.dead`);\n\n    if (!isObject(worker.intent)) {\n      throw new Error(`Canonical session snapshot requires workers[${index}].intent to be an object`);\n    }\n\n    ensureStringAllowEmpty(worker.intent.objective, `workers[${index}].intent.objective`);\n    ensureArrayOfStrings(worker.intent.seedPaths, `workers[${index}].intent.seedPaths`);\n\n    if (!isObject(worker.outputs)) {\n      throw new Error(`Canonical session snapshot requires workers[${index}].outputs to be an object`);\n    }\n\n    ensureArrayOfStrings(worker.outputs.summary, `workers[${index}].outputs.summary`);\n    ensureArrayOfStrings(worker.outputs.validation, `workers[${index}].outputs.validation`);\n    ensureArrayOfStrings(worker.outputs.remainingRisks, `workers[${index}].outputs.remainingRisks`);\n\n    if (!isObject(worker.artifacts)) {\n      throw new Error(`Canonical session snapshot requires workers[${index}].artifacts to be an object`);\n    }\n  });\n\n  if (!isObject(snapshot.aggregates)) {\n    throw new Error('Canonical session snapshot requires aggregates to be an object');\n  }\n\n  ensureInteger(snapshot.aggregates.workerCount, 'aggregates.workerCount');\n  if (snapshot.aggregates.workerCount !== snapshot.workers.length) {\n    throw new Error('Canonical session snapshot requires aggregates.workerCount to match workers.length');","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/session-adapters/canonical-session.js#L205-L241","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Always emit outputs: { summary: [], validation: [], remainingRisks: [] } at minimum.","For dmux, map from worker.handoff.summary/validation/remainingRisks with Array.isArray fallbacks; for claude-history, summary from metadata.completed, remainingRisks from metadata.notes.","If migrating from an older shape, coerce each field to a string array before validation.","Add a schema-shape unit test for every adapter's normalize* output."],"exampleFix":"// before\nconst worker = { id:'w1', label:'w1', state:'recorded', health:'healthy', runtime:{...}, intent:{...}, artifacts:{...} };\n\n// after\nconst worker = {\n  id:'w1', label:'w1', state:'recorded', health:'healthy', runtime:{...}, intent:{...},\n  outputs: {\n    summary: Array.isArray(metadata.completed) ? metadata.completed : [],\n    validation: [],\n    remainingRisks: metadata.notes ? [metadata.notes] : []\n  },\n  artifacts:{...}\n};","handlingStrategy":"type-guard","validationCode":"function ensureWorkerOutputs(worker) {\n  if (!worker.outputs || typeof worker.outputs !== 'object') {\n    worker.outputs = { summary: [], validation: [], remainingRisks: [] };\n  }\n  return worker;\n}","typeGuard":"function hasValidOutputs(w) {\n  const o = w.outputs;\n  if (o === null || typeof o !== 'object' || Array.isArray(o)) return false;\n  return ['summary','validation','remainingRisks'].every(k => Array.isArray(o[k]) && o[k].every(x => typeof x === 'string'));\n}","tryCatchPattern":"try { validateCanonicalSnapshot(snapshot); }\ncatch (err) {\n  if (err.message.includes('.outputs to be an object')) {\n    snapshot.workers.forEach(w => { if (!hasValidOutputs(w)) ensureWorkerOutputs(w); });\n    validateCanonicalSnapshot(snapshot);\n  } else throw err;\n}","preventionTips":["Default outputs to {summary:[],validation:[],remainingRisks:[]} in every worker builder.","Coerce source arrays with Array.isArray fallbacks as the dmux adapter does.","Validate snapshot shape in adapter tests."],"tags":["canonical-session","schema-validation","outputs"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}