{"record":{"id":"ba92e712f5a9765f","repo":"thedotmack/claude-mem","slug":"cloud-sync-canonical-payload-name-must-be-stor","errorCode":null,"errorMessage":"cloud sync canonical payload: ${name} must be stored JSON text","messagePattern":"cloud sync canonical payload: (.+?) must be stored JSON text","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":160,"sourceCode":"  /** Op body per the SyncApply BODY FIELD MAPPING — values exactly as stored. */\n  toBody: (r: LocalRow) => OpBody;\n}\n\nfunction decimalPayload(value: unknown, name: string, nullable = false): string | null {\n  if (value === null || value === undefined) {\n    if (nullable) return null;\n    return '0';\n  }\n  if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {\n    throw new Error(`cloud sync canonical payload: ${name} must be a non-negative safe integer`);\n  }\n  return String(value);\n}\n\nfunction jsonPayloadColumn(value: unknown, name: string, expected: 'array' | 'object'): unknown {\n  if (value === null || value === undefined) return null;\n  if (typeof value !== 'string') {\n    throw new Error(`cloud sync canonical payload: ${name} must be stored JSON text`);\n  }\n  let parsed: unknown;\n  try { parsed = JSON.parse(value); } catch {\n    throw new Error(`cloud sync canonical payload: ${name} is not valid JSON`);\n  }\n  if (expected === 'array' && !Array.isArray(parsed)) {\n    throw new Error(`cloud sync canonical payload: ${name} must decode to an array`);\n  }\n  if (expected === 'object' && (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))) {\n    throw new Error(`cloud sync canonical payload: ${name} must decode to an object`);\n  }\n  return parsed;\n}\n\nconst KINDS: KindSpec[] = [\n  {\n    kind: 'observation',\n    localTable: 'observations',","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/CloudSync.ts#L142-L178","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"-- before\nSELECT id FROM observations WHERE files_read IS NOT NULL AND typeof(files_read) <> 'text';\n-- after\nUPDATE observations SET files_read = json_quote(files_read)\nWHERE files_read IS NOT NULL AND typeof(files_read) IN ('integer','real');","handlingStrategy":"validation","validationCode":"-- Pre-sync check: JSON columns stored as text\nSELECT COUNT(*) AS bad FROM observations\n WHERE (facts IS NOT NULL AND typeof(facts) <> 'text')\n    OR (metadata IS NOT NULL AND typeof(metadata) <> 'text');\n-- bad = 0 before enabling cloud sync","typeGuard":"const isStoredJsonText = (v: unknown): v is string => typeof v === 'string';","tryCatchPattern":"try {\n  await cloudSync.push();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must be stored JSON text')) {\n    // re-serialize the named column with json_quote(), then re-push\n  } else throw e;\n}","preventionTips":["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"],"tags":["cloud-sync","payload-validation","sqlite","column-affinity","blob"],"backgroundTag":"payload-validation-failed","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}