{"record":{"id":"edf7e472a809e0fb","repo":"thedotmack/claude-mem","slug":"cloud-sync-canonical-payload-name-must-decode-edf7e4","errorCode":null,"errorMessage":"cloud sync canonical payload: ${name} must decode to an object","messagePattern":"cloud sync canonical payload: (.+?) must decode to an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":170,"sourceCode":"    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',\n    selectSql: `\n      SELECT CAST(id AS TEXT) AS id, CAST(sync_rev AS TEXT) AS sync_rev,\n        memory_session_id, project, text, type, title, subtitle,\n        facts, narrative, concepts, files_read, files_modified, prompt_number,\n        discovery_tokens, content_hash, generated_by_model, agent_type, agent_id,\n        metadata, merged_into_project, created_at, created_at_epoch\n      FROM observations\n      WHERE synced_at IS NULL AND origin_device_id IS NULL\n      ORDER BY id LIMIT ${BATCH}`,\n    selectOneSql: `","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/CloudSync.ts#L152-L188","documentation":"The object-shaped counterpart for columns like metadata: the text parsed as JSON but decoded to an array, a primitive, or JSON 'null' — the contract requires a plain object. jsonPayloadColumn rejects it with the column name, blocking the push before a malformed op reaches the hub.","triggerScenarios":"metadata stored as '[]' or a bare string/number; a writer storing null as the literal text 'null'; a migration that defaulted the column to an empty array instead of an empty object.","commonSituations":"Components disagreeing whether metadata is a map or a list; seed scripts writing placeholders; older builds encoding null differently.","solutions":["Find offenders: SELECT id FROM <table> WHERE metadata IS NOT NULL AND json_type(metadata) <> 'object'","Repair: UPDATE <table> SET metadata = '{}' WHERE json_type(metadata) <> 'object' (or NULL for rows where metadata is genuinely absent)","Re-run the sync push","Align writers on a single metadata shape (plain object)"],"exampleFix":"-- before\nSELECT id FROM observations WHERE metadata IS NOT NULL AND json_type(metadata) <> 'object';\n-- after\nUPDATE observations SET metadata = '{}'\nWHERE metadata IS NOT NULL AND json_type(metadata) <> 'object';","handlingStrategy":"validation","validationCode":"-- Pre-sync check: object-contract columns decode to objects\nSELECT COUNT(*) AS bad FROM observations\n WHERE metadata IS NOT NULL AND json_type(metadata) <> 'object';\n-- bad = 0 before enabling cloud sync","typeGuard":"const decodesToObject = (v: unknown): v is string =>\n  typeof v === 'string' && (() => {\n    try { const p = JSON.parse(v); return typeof p === 'object' && p !== null && !Array.isArray(p); }\n    catch { return false; }\n  })();","tryCatchPattern":"try {\n  await cloudSync.push();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must decode to an object')) {\n    // normalize metadata to '{}' or NULL, then re-push\n  } else throw e;\n}","preventionTips":["Standardize metadata as a flat JSON object; never an array or scalar","Do not store the literal text 'null' — write SQL NULL instead","Assert json_type() in seeds and imports","Document the metadata shape next to every writer"],"tags":["cloud-sync","payload-validation","json","shape-mismatch","metadata"],"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"}