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

  1. Add aggregates.healths as a plain object of health -> integer counts computed from workers.
  2. Rebuild the entire aggregates object from workers in one place so healths is always present.
  3. Convert Maps to plain objects with Object.fromEntries before persisting.
  4. 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

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


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)