affaan-m/ECC · error · Error

Failed to serialize

Error message

Failed to serialize ${label}: ${error.message}

What it means

stringifyJson wraps JSON.stringify for values stored in JSON columns of the state store. If serialization throws — which happens for circular references, BigInt values, or other JSON-incompatible structures — the error is rethrown with the field label and the underlying message. This keeps inserts/upserts from failing with an opaque TypeError.

Solutions

  1. Remove or replace the circular/non-serializable field before passing the record
  2. Run JSON.stringify(value) yourself in a try/catch to find which label fails, then fix that field
  3. Convert BigInt fields with Number() or String() first; strip functions via a replacer: JSON.stringify(v, (k, val) => typeof val === 'function' ? undefined : val)
  4. For intentionally circular structures, store a flattened projection (ids only) instead of the live object graph

Example fix

// before
upsertSession({ id, snapshot: sessionState }); // sessionState has parent back-reference
// after
upsertSession({ id, snapshot: JSON.parse(JSON.stringify(sessionState, (k, v) => (typeof v === 'bigint' ? String(v) : v))) });
Defensive patterns

Strategy: try-catch

Validate before calling

function isJsonSafe(value, seen = new Set()) {
  if (value === null || typeof value !== 'object') {
    return typeof value !== 'function' && typeof value !== 'bigint';
  }
  if (seen.has(value)) return false;
  seen.add(value);
  return Object.values(value).every(v => isJsonSafe(v, seen));
}

Type guard

const isJsonSafe = (v, seen = new Set()) =>
  v === null || typeof v !== 'object'
    ? typeof v !== 'function' && typeof v !== 'bigint'
    : !seen.has(v) && (seen.add(v), Object.values(v).every(x => isJsonSafe(x, seen)));

Try / catch

try {
  upsertSession(record);
} catch (err) {
  if (err.message.startsWith('Failed to serialize')) {
    console.error(`Field '${err.message.match(/serialize (.*?):/)?.[1]}' is not JSON-safe`);
    record.snapshot = JSON.parse(JSON.stringify(record.snapshot, replacer));
    return upsertSession(record);
  } throw err;
}

Prevention

When it happens

Trigger: Calling insertDecision, insertGovernanceEvent, upsertInstallState, upsertWorkItem, or upsertSession with a payload containing a circular reference (e.g. an object whose parent/child link points back), a BigInt, or a function-valued field.

Common situations: ORM/query builder result rows that include back-references to their client; attaching a logger or config object with circular deps into a snapshot; BigInt ids from a database driver stored raw; passing a class instance with getters that reference the instance itself.

Understand the failure class

Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/a8f6c6421ad99d34. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/state-store/queries.js:36

    throw new Error(`Invalid limit: ${value}`);
  }

  return parsed;
}

function parseJsonColumn(value, fallback) {
  if (value === null || value === undefined || value === '') {
    return fallback;
  }

  return JSON.parse(value);
}

function stringifyJson(value, label) {
  try {
    return JSON.stringify(value);
  } catch (error) {
    throw new Error(`Failed to serialize ${label}: ${error.message}`);
  }
}

function mapSessionRow(row) {
  const snapshot = parseJsonColumn(row.snapshot, {});
  return {
    id: row.id,
    adapterId: row.adapter_id,
    harness: row.harness,
    state: row.state,
    repoRoot: row.repo_root,
    startedAt: row.started_at,
    endedAt: row.ended_at,
    snapshot,
    workerCount: Array.isArray(snapshot && snapshot.workers) ? snapshot.workers.length : 0,
  };
}

View on GitHub (pinned to 8321021c54)