affaan-m/ECC · error · Error
Canonical session snapshot requires aggregates.workerCount…
Error message
Canonical session snapshot requires aggregates.workerCount to match workers.length
What it means
This consistency error fires when snapshot.aggregates.workerCount is an integer but does not equal snapshot.workers.length. The canonical schema treats aggregates.workerCount as a denormalized count of the workers array; the validator rejects any snapshot where the summary and the actual data disagree, since that signals a corrupted or partially updated snapshot.
Solutions
- Recompute aggregates.workerCount = snapshot.workers.length (and rebuild states/healths counts) immediately before persisting.
- Never mutate a loaded snapshot's workers in place; rebuild the whole snapshot object including aggregates.
- Add a helper that derives aggregates from workers so they can never drift.
- Run validateCanonicalSnapshot after every snapshot mutation in tests.
Example fix
// before
snapshot.workers.push(newWorker);
persistCanonicalSnapshot(snapshot); // workerCount stale
// after
snapshot.workers.push(newWorker);
snapshot = { ...snapshot, aggregates: { ...snapshot.aggregates, workerCount: snapshot.workers.length } };
persistCanonicalSnapshot(snapshot); Defensive patterns
Strategy: validation
Validate before calling
if (snapshot.aggregates && snapshot.aggregates.workerCount !== snapshot.workers.length) throw new Error('aggregates.workerCount out of sync with workers.length'); Type guard
function aggregatesInSync(s) { return Number.isInteger(s.aggregates?.workerCount) && s.aggregates.workerCount === s.workers.length; } Try / catch
try { persistCanonicalSnapshot(snapshot); } catch (err) { if (String(err.message).includes('workerCount to match workers.length')) { console.error('Aggregates stale:', err.message); snapshot = recomputeAggregates(snapshot); persistCanonicalSnapshot(snapshot); } else { throw err; } } Prevention
- Treat snapshots as immutable: create a new snapshot object with recomputed aggregates after any worker change.
- Never push/filter workers on a loaded snapshot without rebuilding aggregates.
- Wrap all mutations in a helper that recomputes workerCount, states, and healths together.
- Add a round-trip test: mutate -> validate -> persist.
When it happens
Trigger: Calling persistCanonicalSnapshot after mutating the workers array (push/filter/splice) without recomputing aggregates.workerCount; merging snapshots by concatenating workers while copying stale aggregates.
Common situations: Script that adds a worker to a loaded snapshot and re-persists it without updating counts; a filter that removes dead workers but keeps the old aggregate; concurrent writers where one updated workers and another updated aggregates.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Canonical session snapshot requires aggregates.healths to…
- Canonical session snapshot requires aggregates.states to be…
- Canonical session snapshot requires aggregates to be an…
- Canonical session snapshot requires workers
- Canonical session snapshot requires workers
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/bd8ca5ffacdffa14.
Report an issue: GitHub.
Appendix: 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 8321021c54)