thedotmack/claude-mem · error · Error

canonical content

Error message

canonical content: ${message}

What it means

Internal helper `invalid()` in canonical-content.ts throws this for any violation of the canonical-content rules: non-RFC-8259 JSON values, unsorted object keys, invalid decimal forms, or malformed operation envelopes. Nearly every exported function of the module (canonicalJson, stableDocumentId, wrapCanonicalBody, parseCanonicalOperation, decimal helpers) funnels through it, so the message names the specific invariant that failed.

Solutions

  1. Read the trailing `${message}` in the error to identify which invariant failed (key order, decimal form, missing field).
  2. Pass raw data through `canonicalJson()`/`normalizeJson()` before hashing or building operation payloads.
  3. Represent numeric values as canonical decimal strings (no leading zeros, explicit fraction/exponent rules), never raw JS numbers.
  4. Validate operation envelopes against the required-field list (`payload_sha256`, etc.) before `parseCanonicalOperation`.

Example fix

// before
const id = stableDocumentId(userDoc); // throws: canonical content: keys not sorted

// after
const canonical = canonicalJson(userDoc);
const id = stableDocumentId(JSON.parse(canonical)); // normalized first
Defensive patterns

Strategy: validation

Validate before calling

// validate inputs before calling canonical-content APIs
function assertCanonicalizable(value: unknown): void {
  if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
    throw new Error('value is not RFC-8259 JSON serializable');
  }
  if (typeof value === 'number' && (!Number.isFinite(value) || Object.is(value, -0))) {
    throw new Error('numbers must be finite and not -0');
  }
}
function assertDecimalString(s: string): void {
  if (!/^-?(0|[1-9]\d*)(\.\d+)?$/.test(s)) throw new Error(`non-canonical decimal: ${s}`);
}

Type guard

function isPlainJsonObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v) &&
    Object.getPrototypeOf(v) === Object.prototype;
}

Try / catch

try {
  const canonical = canonicalJson(rawDocument);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('canonical content: ')) {
    console.error(`document rejected by canonicalizer: ${err.message}`);
    // fall back to re-serializing through JSON.parse(JSON.stringify(...)) then retry once
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `canonicalJson` with a value JSON.stringify can't represent deterministically (undefined, functions, cycles via normalizeJson's checks), passing a decimal string with leading zeros or non-canonical exponent form to `assertCanonicalDecimal`/`incrementCanonicalDecimal`, or feeding `parseCanonicalOperation` an object missing required fields like `payload_sha256`.

Common situations: Feeding user-supplied JSON that hasn't been normalized (duplicate/unsorted keys, -0, non-finite numbers); hand-built operations that skip canonicalization before hashing; upgrading the library and old persisted documents no longer satisfy stricter canonical rules; accidentally passing a JS number where a canonical decimal string is expected.

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/28a5390ddd6c92da. Report an issue: GitHub.

Appendix: source

Thrown at workers/sync-hub/src/canonical-content.ts:59

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;

const encoder = new TextEncoder();

function invalid(message: string): never {
	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)) invalid("numbers must be finite");
		if (!Number.isSafeInteger(value) && Number.isInteger(value)) {
			invalid("integers must be safe; use decimal strings for uint64 values");
		}
		if (Object.is(value, -0)) return 0;
		return value;
	}
	if (typeof value !== "object") invalid(`unsupported JSON value ${typeof value}`);

View on GitHub (pinned to d8bc9755e7)