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
- Read the trailing message after 'canonical content: ' — it names the exact rule violated.
- Pre-sanitize values: replace NaN/Infinity with strings or nulls, break cycles, and only pass JSON-safe data.
- Ensure decimals are passed as strings (canonical decimal form) rather than JS numbers.
- 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
- Pass decimals as strings and timestamps as finite integers into sync payloads.
- Never feed runtime objects with cycles, functions, NaN, or Infinity into canonicalJson.
- Keep payload_schema_version / payload_sha256 fields present and up to date.
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
- canonical content
- CLAUDE_MEM_TELEGRAM_WRAPUP_ROUTES must be a JSON object
- cloud sync canonical payload
- device_id must be 1-128 characters
- deviceId must be non-empty
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)