{"record":{"id":"c3eaf1c7886760d0","repo":"affaan-m/ECC","slug":"canonical-session-snapshot-requires-workers-inde","errorCode":null,"errorMessage":"Canonical session snapshot requires workers[${index}] to be an object","messagePattern":"Canonical session snapshot requires workers\\[(.+?)\\] to be an object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/session-adapters/canonical-session.js","lineNumber":196,"sourceCode":"  ensureString(snapshot.session.id, 'session.id');\n  ensureString(snapshot.session.kind, 'session.kind');\n  ensureString(snapshot.session.state, 'session.state');\n  ensureOptionalString(snapshot.session.repoRoot, 'session.repoRoot');\n\n  if (!isObject(snapshot.session.sourceTarget)) {\n    throw new Error('Canonical session snapshot requires session.sourceTarget to be an object');\n  }\n\n  ensureString(snapshot.session.sourceTarget.type, 'session.sourceTarget.type');\n  ensureString(snapshot.session.sourceTarget.value, 'session.sourceTarget.value');\n\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","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/session-adapters/canonical-session.js#L178-L214","documentation":"Thrown by validateCanonicalSnapshot() while iterating snapshot.workers: an entry at position `index` failed the isObject() check (it was null, an array, or a primitive). The canonical schema (ecc.session.v1) requires every workers[] entry to be a plain object with id/label/state/health/runtime/intent/outputs/artifacts. Validation runs both on hand-supplied snapshots and as a self-check inside every normalize*Session builder before persist.","triggerScenarios":"Calling validateCanonicalSnapshot(snapshot) or persistCanonicalSnapshot(snapshot) where snapshot.workers contains a non-object element (e.g. workers:[null] or workers:[\"w1\"]). Reading a recorded canonical snapshot JSON from disk whose workers array was hand-edited or written by an older adapter that emitted null placeholders for dead panes.","commonSituations":"A future or custom adapter maps a missing dmux pane to null in the workers array instead of skipping it. A user reloads a persisted recording that was truncated/corrupted on write. Snapshot is assembled manually from partial data and a worker slot is left as a placeholder string.","solutions":["Inspect snapshot.workers[index]: if null/array/primitive, either drop the entry or rebuild it as an object matching the worker shape (id,label,state,health,runtime,intent,outputs,artifacts).","If the snapshot came from a recording file, open the JSON and confirm workers[] entries are objects; re-record from the live adapter if corrupted.","When building a custom adapter, filter workers before validation: workers = rawWorkers.filter(w => w && typeof w === 'object').","Reproduce with a minimal snapshot and print JSON.stringify(snapshot.workers[index], null, 2) to see exactly what was passed."],"exampleFix":"// before\nconst snapshot = {\n  schemaVersion: 'ecc.session.v1',\n  adapterId: 'custom',\n  session: { id: 's1', kind: 'custom', state: 'active', sourceTarget: { type: 'custom', value: 's1' } },\n  workers: [null], // throws at index 0\n  aggregates: { workerCount: 1, states: {}, healths: {} }\n};\n\n// after: drop or materialize the placeholder\nconst workers = rawWorkers\n  .filter(w => w && typeof w === 'object')\n  .map(materializeWorker);\nconst snapshot = {\n  schemaVersion: 'ecc.session.v1',\n  adapterId: 'custom',\n  session: { id: 's1', kind: 'custom', state: 'active', sourceTarget: { type: 'custom', value: 's1' } },\n  workers,\n  aggregates: buildAggregates(workers)\n};","handlingStrategy":"validation","validationCode":"function sanitizeWorkers(workers) {\n  if (!Array.isArray(workers)) return [];\n  return workers.filter(w => w !== null && typeof w === 'object' && !Array.isArray(w));\n}\n// before persisting:\nsnapshot.workers = sanitizeWorkers(snapshot.workers);\nsnapshot.aggregates = buildAggregates(snapshot.workers);","typeGuard":"function isPlainObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }\nfunction isWorkerEntry(w) {\n  return isPlainObject(w)\n    && typeof w.id === 'string' && typeof w.label === 'string'\n    && isPlainObject(w.runtime) && isPlainObject(w.intent)\n    && isPlainObject(w.outputs) && isPlainObject(w.artifacts);\n}","tryCatchPattern":"try { validateCanonicalSnapshot(snapshot); }\ncatch (err) {\n  if (err.message.includes('workers[')) {\n    // log the failing index from the message, sanitize, and rebuild aggregates\n    snapshot.workers = (snapshot.workers||[]).filter(isWorkerEntry);\n    snapshot.aggregates = buildAggregates(snapshot.workers);\n    validateCanonicalSnapshot(snapshot);\n  } else throw err;\n}","preventionTips":["Always build workers via the normalize*Session helpers rather than constructing them inline.","Treat snapshots as immutable: derive workers and aggregates in the same step.","Add a unit test per adapter that runs validateCanonicalSnapshot on the normalize* output."],"tags":["canonical-session","schema-validation","workers"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}