thedotmack/claude-mem · error · Error
cloud sync canonical payload: ${name} must be stored JSON te
Error message
cloud sync canonical payload: ${name} must be stored JSON text What it means
jsonPayloadColumn requires the JSON-typed columns (facts, concepts, files_read, files_modified, metadata) to come back from SQLite as TEXT containing JSON. If the driver hands over a non-string (BLOB, INTEGER, REAL, or an already-decoded object), the canonical op body cannot be built and the push aborts with this error naming the column — the column's physical storage no longer matches the storage contract.
Source
Thrown at src/services/sync/CloudSync.ts:160
/** Op body per the SyncApply BODY FIELD MAPPING — values exactly as stored. */
toBody: (r: LocalRow) => OpBody;
}
function decimalPayload(value: unknown, name: string, nullable = false): string | null {
if (value === null || value === undefined) {
if (nullable) return null;
return '0';
}
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
throw new Error(`cloud sync canonical payload: ${name} must be a non-negative safe integer`);
}
return String(value);
}
function jsonPayloadColumn(value: unknown, name: string, expected: 'array' | 'object'): unknown {
if (value === null || value === undefined) return null;
if (typeof value !== 'string') {
throw new Error(`cloud sync canonical payload: ${name} must be stored JSON text`);
}
let parsed: unknown;
try { parsed = JSON.parse(value); } catch {
throw new Error(`cloud sync canonical payload: ${name} is not valid JSON`);
}
if (expected === 'array' && !Array.isArray(parsed)) {
throw new Error(`cloud sync canonical payload: ${name} must decode to an array`);
}
if (expected === 'object' && (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))) {
throw new Error(`cloud sync canonical payload: ${name} must decode to an object`);
}
return parsed;
}
const KINDS: KindSpec[] = [
{
kind: 'observation',
localTable: 'observations',View on GitHub (pinned to e2d1df569a)
Solutions
- Check storage types: SELECT id, typeof(<col>) FROM <table> WHERE <col> IS NOT NULL AND typeof(<col>) <> 'text'
- Re-serialize offending values to JSON text: UPDATE <table> SET <col> = json_quote(<col>) WHERE typeof(<col>) IN ('integer','real')
- Confirm migrations keep these columns TEXT affinity and writers always store serialized JSON
- Re-run the sync push
Example fix
-- before
SELECT id FROM observations WHERE files_read IS NOT NULL AND typeof(files_read) <> 'text';
-- after
UPDATE observations SET files_read = json_quote(files_read)
WHERE files_read IS NOT NULL AND typeof(files_read) IN ('integer','real'); Defensive patterns
Strategy: validation
Validate before calling
-- Pre-sync check: JSON columns stored as text
SELECT COUNT(*) AS bad FROM observations
WHERE (facts IS NOT NULL AND typeof(facts) <> 'text')
OR (metadata IS NOT NULL AND typeof(metadata) <> 'text');
-- bad = 0 before enabling cloud sync Type guard
const isStoredJsonText = (v: unknown): v is string => typeof v === 'string';
Try / catch
try {
await cloudSync.push();
} catch (e) {
if (e instanceof Error && e.message.includes('must be stored JSON text')) {
// re-serialize the named column with json_quote(), then re-push
} else throw e;
} Prevention
- Always JSON.stringify before writing these columns; never write raw objects/numbers
- Declare these columns TEXT in migrations; verify affinity after restores
- Use the same serialization helper in every writer
- Check typeof() after bulk imports from external tools
When it happens
Trigger: The column was written as BLOB by another tool; the column holds a bare number or NULL-ish scalar because a migration or affinity change converted it; better-sqlite3 returning a Buffer for blob-affinity columns; a writer using a different serialization.
Common situations: Schema drift after an upgrade changed column affinity; a third-party tool imported rows with different typing; the DB was reconstructed from a dump that lost TEXT affinity.
Related errors
- cloud sync canonical payload: ${name} must be a non-negative
- cloud sync canonical payload: ${name} must decode to an arra
- cloud sync canonical payload: ${name} is not valid JSON
- cloud sync canonical payload: ${name} must decode to an obje
- Invalid CLAUDE_MEM_QUEUE_ENGINE=${raw}; expected sqlite or b
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/ba92e712f5a9765f.
Report an issue: GitHub.