affaan-m/ECC · error · Error
Canonical session snapshot requires aggregates.workerCount t
Error message
Canonical session snapshot requires aggregates.workerCount to match workers.length
What it means
aggregates.workerCount is a valid non-negative integer but does not equal snapshot.workers.length. This is a consistency check: aggregates must reflect the same worker set as workers[]. The built-in buildAggregates sets workerCount = workers.length, so a mismatch means aggregates were computed against a different (stale or hand-edited) worker list.
Source
Thrown at scripts/lib/session-adapters/canonical-session.js:241
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');
}
for (const [state, count] of Object.entries(snapshot.aggregates.states)) {
ensureString(state, 'aggregates.states key');
ensureInteger(count, `aggregates.states.${state}`);
}
for (const [health, count] of Object.entries(snapshot.aggregates.healths)) {
ensureString(health, 'aggregates.healths key');
ensureInteger(count, `aggregates.healths.${health}`);View on GitHub (pinned to 01e15490f0)
Solutions
- Recompute aggregates immediately before validation: snapshot.aggregates = buildAggregates(snapshot.workers).
- Never mutate workers after buildAggregates; treat snapshots as immutable and rebuild aggregates on any change.
- If filtering workers, rebuild aggregates from the filtered array in the same step.
- Add an assertion in adapter tests: expect(snapshot.aggregates.workerCount).toBe(snapshot.workers.length).
Example fix
// before
const workers = rawWorkers.filter(w => w && typeof w === 'object');
const snapshot = { schemaVersion:'ecc.session.v1', adapterId:'custom', session:{...}, workers, aggregates: cachedAggregates };
// after: recompute after final worker list is settled
const workers = rawWorkers.filter(w => w && typeof w === 'object');
const snapshot = {
schemaVersion:'ecc.session.v1', adapterId:'custom', session:{...}, workers,
aggregates: buildAggregates(workers)
}; Defensive patterns
Strategy: validation
Validate before calling
function reconcileAggregates(snapshot) {
if (!snapshot.aggregates || typeof snapshot.aggregates !== 'object') {
snapshot.aggregates = buildAggregates(snapshot.workers || []);
} else {
snapshot.aggregates.workerCount = (snapshot.workers || []).length;
}
return snapshot;
} Type guard
function workerCountMatches(s) {
return s.aggregates && Number.isInteger(s.aggregates.workerCount)
&& s.aggregates.workerCount === (s.workers || []).length;
} Try / catch
try { validateCanonicalSnapshot(snapshot); }
catch (err) {
if (err.message.includes('workerCount to match')) {
snapshot.aggregates = buildAggregates(snapshot.workers || []);
validateCanonicalSnapshot(snapshot);
} else throw err;
} Prevention
- Treat snapshots as immutable; never mutate workers after computing aggregates.
- Filter/transform workers and rebuild aggregates in the same expression.
- Assert workerCount === workers.length in adapter tests.
When it happens
Trigger: Workers were added/removed after aggregates were computed (e.g. aggregates built from a cached list, then workers re-filtered). Hand-editing a recording JSON to add a worker without updating workerCount. Concurrent mutation of the workers array between buildAggregates and validateCanonicalSnapshot.
Common situations: Race in an adapter that builds aggregates early then filters dead workers. Copy-paste fixture where workers array was trimmed but workerCount was not. Migration script that transforms workers but copies aggregates verbatim.
Related errors
- Canonical session snapshot requires aggregates to be an obje
- Canonical session snapshot requires aggregates.states to be
- Canonical session snapshot requires aggregates.healths to be
- Canonical session snapshot requires workers[${index}] to be
- Canonical session snapshot requires workers[${index}].runtim
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/bd8ca5ffacdffa14.
Report an issue: GitHub.