thedotmack/claude-mem · error · Error

canonical content

Error message

canonical content: ${message}

What it means

jsonError is the single rejection helper in CanonicalContent.ts: every canonical-content validator (normalizeJson, assertCanonicalDecimal, stableDocumentId, operation builders) throws `canonical content: <reason>` when input does not satisfy the canonical RFC-8259 / deterministic-serialization rules used for cloud sync payloads. It logs the reason at debug level before throwing.

Solutions

  1. Read the trailing message after 'canonical content: ' — it names the exact rule violated.
  2. Pre-sanitize values: replace NaN/Infinity with strings or nulls, break cycles, and only pass JSON-safe data.
  3. Ensure decimals are passed as strings (canonical decimal form) rather than JS numbers.
  4. Confirm payload_schema_version and payload_sha256 are present and current in documents sent to sync.

Example fix

// before
canonicalJson({ ts: Date.parse('not-a-date') }); // NaN -> throws
// after
const ts = Number.isFinite(parsedTs) ? parsedTs : 0;
canonicalJson({ ts });
Defensive patterns

Strategy: validation

Validate before calling

function isJsonSafe(v: unknown, seen = new Set()): boolean {
  if (v === null || typeof v === 'boolean' || typeof v === 'string') return true;
  if (typeof v === 'number') return Number.isFinite(v);
  if (typeof v !== 'object' || seen.has(v)) return false;
  seen.add(v);
  return Object.values(v as object).every(x => isJsonSafe(x, seen));
}
if (!isJsonSafe(doc)) sanitizeBeforeSync(doc);

Type guard

function isCanonicalDecimal(v: unknown): v is string {
  return typeof v === 'string' && /^-?\d+(\.\d+)?$/.test(v);
}

Try / catch

try {
  const canonical = canonicalJson(doc);
} catch (e) {
  logger.warn('Skipping non-canonical document', { reason: e.message });
  return null; // or fix/sanitize and retry
}

Prevention

When it happens

Trigger: Calling canonicalJson with non-JSON values (undefined, functions, cyclic structures, NaN/Infinity), normalizeJson with duplicate or non-canonical structures it cannot canonicalize, assertCanonicalDecimal with a non-decimal value, or buildContentOperation/buildMutationOperation with payloads failing schema checks (missing payload_schema_version / payload_sha256 shape).

Common situations: Syncing documents produced by hand-edited JSON, values like NaN or 1e999 that serialize to null, cyclic objects from runtime state, or cloud-sync responses/records written by a newer client with a different payload schema.

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 thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/127901adc055f748. Report an issue: GitHub.

Appendix: source

Thrown at src/services/sync/CanonicalContent.ts:67

const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const ENVELOPE_KEYS = [
  'body_schema_version',
  'deleted',
  'deleted_at',
  'entity_rev',
  'id',
  'kind',
  'mutation',
  'origin_device_id',
  'origin_local_id',
  'payload',
  'payload_schema_version',
  'payload_sha256',
] as const;

function jsonError(message: string): never {
  logger.debug('CLOUD_SYNC', 'Rejected invalid canonical content', { reason: message });
  throw new Error(`canonical content: ${message}`);
}

/** RFC-8259 JSON with recursively sorted object keys and preserved array order. */
export function canonicalJson(value: unknown): string {
  return JSON.stringify(normalizeJson(value, new Set()));
}

function normalizeJson(value: unknown, seen: Set<object>): unknown {
  if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) jsonError('numbers must be finite');
    if (!Number.isSafeInteger(value) && Number.isInteger(value)) {
      jsonError('integers must be safe; use decimal strings for uint64 values');
    }
    if (Object.is(value, -0)) return 0;
    return value;
  }
  if (typeof value !== 'object') jsonError(`unsupported JSON value ${typeof value}`);

View on GitHub (pinned to d8bc9755e7)