{"record":{"id":"8dd3cbe880f7d935","repo":"thedotmack/claude-mem","slug":"syncapply-invalid-op-seq-op-seq-kind-op-kind-origin-op-8dd3cb","errorCode":null,"errorMessage":"SyncApply: invalid op seq=${op.seq} kind=${op.kind} origin=${op.origin_device}/${op.origin_id}: remap_project fields must set project and/or merged_into_project","messagePattern":"SyncApply: invalid op seq=(.+?) kind=(.+?) origin=(.+?)/(.+?): remap_project fields must set project and/or merged_into_project","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/SyncApply.ts","lineNumber":1101,"sourceCode":"    const fields = body.fields as Record<string, unknown> | undefined;\n    if (!where || typeof where !== 'object' || !fields || typeof fields !== 'object') {\n      throw invalidOp(op, 'remap_project requires where and fields objects');\n    }\n\n    const setClauses: string[] = [];\n    const setParams: (string | number)[] = [];\n    const newProject = fieldString(op, fields, 'project');\n    const mergedInto = fieldString(op, fields, 'merged_into_project');\n    if (newProject !== null) {\n      setClauses.push('project = ?');\n      setParams.push(newProject);\n    }\n    if (mergedInto !== null) {\n      setClauses.push('merged_into_project = ?');\n      setParams.push(mergedInto);\n    }\n    if (setClauses.length === 0) {\n      throw invalidOp(op, 'remap_project fields must set project and/or merged_into_project');\n    }\n\n    const whereClauses: string[] = [];\n    const whereParams: (string | number)[] = [];\n    const whereProject = fieldString(op, where, 'project');\n    const whereMemorySessionId = fieldString(op, where, 'memory_session_id');\n    if (whereProject !== null) {\n      whereClauses.push('project = ?');\n      whereParams.push(whereProject);\n    }\n    if (whereMemorySessionId !== null) {\n      whereClauses.push('memory_session_id = ?');\n      whereParams.push(whereMemorySessionId);\n    }\n    if (where.merged_into_project_is_null === true) {\n      whereClauses.push('merged_into_project IS NULL');\n    }\n    if (whereClauses.length === 0) {","sourceCodeStart":1083,"sourceCodeEnd":1119,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/SyncApply.ts#L1083-L1119","documentation":"Even when fields is a valid object, remap_project must actually assign at least one of the two supported columns: project or merged_into_project. fieldString returns null for both when the keys are missing/non-string, leaving setClauses empty — the code then throws rather than issuing a no-op UPDATE.","triggerScenarios":"A remap_project op whose fields object contains only unrecognized keys (e.g. { name: 'x' }), or sets both project and merged_into_project to null/non-string values.","commonSituations":"Producer renamed fields after a schema change (e.g. new_project instead of project); op intended to clear values sent with null instead of valid identifiers; copy-pasted op template with placeholder fields.","solutions":["Set fields.project and/or fields.merged_into_project to non-null string values","Update producers using old/renamed field names to the supported keys","If no remap is needed, do not enqueue a remap_project op at all"],"exampleFix":"// before\nfields: { new_project: 'proj-b' }\n// after\nfields: { project: 'proj-b' }","handlingStrategy":"validation","validationCode":"function remapSetsSomething(fields: Record<string, unknown>): boolean {\n  return typeof fields.project === 'string' || typeof fields.merged_into_project === 'string';\n}\nif (!remapSetsSomething(op.body.fields)) throw new Error('remap must set project or merged_into_project');","typeGuard":"const hasRemapField = (f: unknown): f is { project?: string; merged_into_project?: string } =>\n  typeof f === 'object' && f !== null &&\n  ('project' in f || 'merged_into_project' in f);","tryCatchPattern":"try { apply(op); } catch (e) {\n  if (e instanceof InvalidOpError && e.reason.includes('must set project and/or merged_into_project')) {\n    log.warn(`no-op remap op seq=${e.seq} discarded`);\n  } else throw e;\n}","preventionTips":["Use shared field-name constants between producer and consumer","Skip enqueuing a remap op when no column actually changes","Add a producer-side assertion before append","Replace null coalescing on the producer with explicit identifiers"],"tags":["sync","validation","fields"],"backgroundTag":"missing-required-argument","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"}