affaan-m/ECC · error · Error
Canonical session snapshot requires aggregates.healths to…
Error message
Canonical session snapshot requires aggregates.healths to be an object
What it means
validateCanonicalSnapshot requires snapshot.aggregates.healths to be a plain object mapping health values to integer counts. This error fires when aggregates.healths is missing, null, or not a plain object. Like states, the healths histogram is a mandatory roll-up of worker.health values.
Solutions
- Add aggregates.healths as a plain object of health -> integer counts computed from workers.
- Rebuild the entire aggregates object from workers in one place so healths is always present.
- Convert Maps to plain objects with Object.fromEntries before persisting.
- Validate snapshots after construction and in tests.
Example fix
// before
const aggregates = { workerCount: workers.length, states: {} };
// after
const healths = workers.reduce((acc, w) => { acc[w.health] = (acc[w.health] || 0) + 1; return acc; }, {});
const aggregates = { workerCount: workers.length, states: {}, healths }; Defensive patterns
Strategy: validation
Validate before calling
if (!snapshot.aggregates?.healths || typeof snapshot.aggregates.healths !== 'object' || Array.isArray(snapshot.aggregates.healths)) throw new Error('aggregates.healths must be a plain object'); Type guard
function hasHealthsHistogram(a) { return !!a && typeof a.healths === 'object' && a.healths !== null && !Array.isArray(a.healths); } Try / catch
try { persistCanonicalSnapshot(snapshot); } catch (err) { if (String(err.message).includes('aggregates.healths to be an object')) { console.error('Missing healths histogram:', err.message); /* derive healths from workers and retry */ } else { throw err; } } Prevention
- Derive healths in the same helper that derives states so neither is omitted.
- Use plain objects, never Maps or arrays, for histogram fields.
- Regenerate old-format snapshots via current adapters.
- Assert full aggregate shape in unit tests.
When it happens
Trigger: Persisting a snapshot whose aggregates omits healths; assigning a Map, null, or array; manually copying workerCount/states but forgetting healths.
Common situations: Partial aggregates written by custom scripts; schema drift from older snapshot versions; lossy JSON transforms dropping healths.
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.states to be…
- 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/03cab321455250a4.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/session-adapters/canonical-session.js:249
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;
}
function resolveRecordingDir(options = {}) {
if (typeof options.recordingDir === 'string' && options.recordingDir.length > 0) {
return path.resolve(options.recordingDir);View on GitHub (pinned to 8321021c54)