{"record":{"id":"a99437ca8692eca0","repo":"thedotmack/claude-mem","slug":"syncapply-invalid-op-seq-op-seq-kind-op-kind-origin-op-a99437","errorCode":null,"errorMessage":"SyncApply: invalid op seq=${op.seq} kind=${op.kind} origin=${op.origin_device}/${op.origin_id}: body is not parseable JSON","messagePattern":"SyncApply: invalid op seq=(.+?) kind=(.+?) origin=(.+?)/(.+?): body is not parseable JSON","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/SyncApply.ts","lineNumber":520,"sourceCode":"      }).catch((error) => {\n        logger.error('SYNC_APPLY', 'Chroma forward of applied row failed, continuing without vector search', {},\n          error instanceof Error ? error : new Error(String(error)));\n      });\n    }\n\n    return result;\n  }\n\n  // -------------------------------------------------------------------------\n  // Row ops\n  // -------------------------------------------------------------------------\n\n  private parseBody(op: SyncOp): Record<string, unknown> {\n    let parsed: unknown;\n    try {\n      parsed = JSON.parse(op.body);\n    } catch {\n      throw invalidOp(op, 'body is not parseable JSON');\n    }\n    if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n      throw invalidOp(op, 'body must be a JSON object');\n    }\n    return parsed as Record<string, unknown>;\n  }\n\n  private findByOrigin(table: string, originDevice: string, originId: string): RowIdRev | undefined {\n    return this.db.prepare(\n      `SELECT id, CAST(sync_rev AS TEXT) AS sync_rev\n       FROM ${table} WHERE origin_device_id = ? AND origin_local_id = ?`\n    ).get(originDevice, originId) as RowIdRev | undefined;\n  }\n\n  /**\n   * Canonical-v2 head ledger. It survives local row deletion and epoch replay,\n   * so a stale live op cannot resurrect a tombstoned entity.\n   */","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/SyncApply.ts#L502-L538","documentation":"parseBody() JSON-parses each sync op's body column. It throws when op.body is not valid JSON at all — e.g. raw text, truncated data, or binary bytes stored in the body field. The op is rejected rather than partially applied.","triggerScenarios":"The sync log row's body column contains malformed or truncated JSON — a crashed writer, manual DB edit, a queue message that was cut off, or a body stored with a non-JSON encoding.","commonSituations":"A migration or import script wrote plain text into body; a producer logged debug output instead of JSON.stringify'd payload; network payload was truncated before persistence; double-encoding bugs where a quoted string of JSON got mangled.","solutions":["Check op.body for seq=${op.seq} in the sync log and fix or delete the corrupt row, then re-pull the op from the origin device.","Fix the producer to always persist JSON.stringify(body) — never raw objects or formatted text.","If rows were truncated by a DB migration, restore from backup and re-run sync.","Add a producer-side assertion that the serialized body round-trips through JSON.parse before enqueueing."],"exampleFix":"// before\nawait log.insert({ body: `user asked about sync` });\n// after\nawait log.insert({ body: JSON.stringify({ op: 'set_title', text: 'user asked about sync' }) });","handlingStrategy":"try-catch","validationCode":"function bodyParses(body) {\n  try { JSON.parse(body); return true; } catch { return false; }\n}\nif (!bodyParses(op.body)) deadLetterQueue.push(op);","typeGuard":"function isParseableObject(body) {\n  try {\n    const p = JSON.parse(body);\n    return typeof p === 'object' && p !== null && !Array.isArray(p);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  applySyncOp(op);\n} catch (e) {\n  if (String(e.message).includes('body is not parseable JSON')) {\n    logger.error({ seq: op.seq }, 'corrupt body — quarantining op');\n    quarantine(op);\n  } else throw e;\n}","preventionTips":["Always JSON.stringify on the producer; never store raw text/objects in body","Round-trip test: JSON.parse(JSON.stringify(body)) before persisting","Monitor for truncated writes; use transactions when inserting sync log rows"],"tags":["sync","json","corruption"],"backgroundTag":"json-parse-error","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}