affaan-m/ECC · error · Error
Canonical session snapshot requires aggregates.states to be…
Error message
Canonical session snapshot requires aggregates.states to be an object
What it means
validateCanonicalSnapshot requires snapshot.aggregates.states to be a plain object mapping state names to integer counts. This error fires when aggregates.states is missing, null, or a non-object. The states histogram lets consumers render per-state worker counts without rescanning the workers array.
Solutions
- Add aggregates.states as a plain object of state -> integer counts derived from the workers array.
- Convert any Map/arr form to a plain object (Object.fromEntries) before persisting.
- Rebuild the whole aggregates block from workers in one helper so states is never omitted.
- Validate snapshots post-construction.
Example fix
// before
aggregates.states = new Map([['idle', 2]]);
// after
aggregates.states = Object.fromEntries(
Object.entries(workers.reduce((acc, w) => { acc[w.state] = (acc[w.state] || 0) + 1; return acc; }, {}))
); Defensive patterns
Strategy: validation
Validate before calling
if (!snapshot.aggregates?.states || typeof snapshot.aggregates.states !== 'object' || Array.isArray(snapshot.aggregates.states)) throw new Error('aggregates.states must be a plain object'); Type guard
function hasStatesHistogram(a) { return !!a && typeof a.states === 'object' && a.states !== null && !Array.isArray(a.states); } Try / catch
try { persistCanonicalSnapshot(snapshot); } catch (err) { if (String(err.message).includes('aggregates.states to be an object')) { console.error('Missing states histogram:', err.message); /* derive states from workers and retry */ } else { throw err; } } Prevention
- Use Object.fromEntries(map) when converting Map-based counts to plain objects.
- Always build all three aggregates fields (workerCount, states, healths) together.
- Round-trip snapshots through JSON to catch non-serializable types before persisting.
- Validate snapshots in tests.
When it happens
Trigger: Persisting a snapshot whose aggregates block includes workerCount but omits states; assigning states as a Map or array instead of a plain object (e.g. after JSON round-trip of a Map).
Common situations: Hand-built aggregates that only set workerCount; JSON.stringify of a Map producing {} silently; older snapshot versions with a different aggregates shape.
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 to be an…
- Canonical session snapshot requires aggregates.workerCount…
- 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/93df044b91e0c325.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/session-adapters/canonical-session.js:245
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}`);
}
return snapshot;
}View on GitHub (pinned to 8321021c54)