{"record":{"id":"c54b1bf5f75c4f4e","repo":"affaan-m/ECC","slug":"canonical-session-snapshot-requires-workers-inde-c54b1b","errorCode":null,"errorMessage":"Canonical session snapshot requires workers[${index}].runtime to be an object","messagePattern":"Canonical session snapshot requires workers\\[(.+?)\\]\\.runtime to be an object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/session-adapters/canonical-session.js","lineNumber":207,"sourceCode":"\n  if (!Array.isArray(snapshot.workers)) {\n    throw new Error('Canonical session snapshot requires workers to be an array');\n  }\n\n  snapshot.workers.forEach((worker, index) => {\n    if (!isObject(worker)) {\n      throw new Error(`Canonical session snapshot requires workers[${index}] to be an object`);\n    }\n\n    ensureString(worker.id, `workers[${index}].id`);\n    ensureString(worker.label, `workers[${index}].label`);\n    ensureString(worker.state, `workers[${index}].state`);\n    ensureString(worker.health, `workers[${index}].health`);\n    ensureOptionalString(worker.branch, `workers[${index}].branch`);\n    ensureOptionalString(worker.worktree, `workers[${index}].worktree`);\n\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","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/session-adapters/canonical-session.js#L189-L225","documentation":"A workers[index] entry passed the object check but its `runtime` field failed isObject(). The schema requires worker.runtime to be an object containing kind (string), command (string|null), active (boolean), and dead (boolean). This guard fires before the per-field ensure* checks on runtime, so any non-object runtime (undefined, null, string) is rejected.","triggerScenarios":"A custom adapter builds a worker object but forgets the runtime block, or sets runtime:null. Hand-authored snapshot JSON omits the runtime key. A migrated snapshot from a pre-runtime schema version where runtime was a string command name.","commonSituations":"Extending the dmux-tmux adapter to a new pane source and forgetting to populate runtime. Editing a recording JSON by hand and deleting the runtime object. Caching a worker across schema versions without re-adding runtime.","solutions":["Ensure each worker has runtime: { kind: '<string>', command: <string|null>, active: <boolean>, dead: <boolean> }.","If adapting a dmux pane, follow normalizeDmuxSnapshot: set kind:'tmux-pane', command from pane.currentCommand||null, active/dead from pane booleans.","If the source genuinely has no runtime, still emit a minimal runtime object (e.g. kind:'unknown', command:null, active:false, dead:true) rather than omitting it.","Validate with a tiny unit snapshot before persisting to surface the exact missing field."],"exampleFix":"// before\nconst worker = { id: 'w1', label: 'w1', state: 'active', health: 'healthy', runtime: null, intent: {...}, outputs: {...}, artifacts: {...} };\n\n// after\nconst worker = {\n  id: 'w1', label: 'w1', state: 'active', health: 'healthy',\n  runtime: { kind: 'tmux-pane', command: pane.currentCommand || null, active: Boolean(pane.active), dead: Boolean(pane.dead) },\n  intent: {...}, outputs: {...}, artifacts: {...}\n};","handlingStrategy":"type-guard","validationCode":"function ensureWorkerRuntime(worker) {\n  if (!worker.runtime || typeof worker.runtime !== 'object') {\n    worker.runtime = { kind: 'unknown', command: null, active: false, dead: true };\n  }\n  return worker;\n}","typeGuard":"function hasValidRuntime(w) {\n  const r = w.runtime;\n  return r !== null && typeof r === 'object' && !Array.isArray(r)\n    && typeof r.kind === 'string'\n    && typeof r.active === 'boolean' && typeof r.dead === 'boolean';\n}","tryCatchPattern":"try { validateCanonicalSnapshot(snapshot); }\ncatch (err) {\n  if (err.message.includes('.runtime to be an object')) {\n    snapshot.workers.forEach(w => { if (!hasValidRuntime(w)) ensureWorkerRuntime(w); });\n    validateCanonicalSnapshot(snapshot);\n  } else throw err;\n}","preventionTips":["Centralize worker construction in one builder per adapter.","Always emit a runtime object even for dead/recorded workers.","Run validateCanonicalSnapshot in adapter unit tests."],"tags":["canonical-session","schema-validation","runtime"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}