{"record":{"id":"492045ed5ca662ec","repo":"thedotmack/claude-mem","slug":"syncapply-invalid-op-seq-op-seq-kind-op-kind-origin-op-492045","errorCode":null,"errorMessage":"SyncApply: invalid op seq=${op.seq} kind=${op.kind} origin=${op.origin_device}/${op.origin_id}: prompt body requires content_session_id, prompt_number, prompt_text, created_at_epoch","messagePattern":"SyncApply: invalid op seq=(.+?) kind=(.+?) origin=(.+?)/(.+?): prompt body requires content_session_id, prompt_number, prompt_text, created_at_epoch","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/SyncApply.ts","lineNumber":893,"sourceCode":"    // it once the origin registers).\n    const contentSessionId = fieldString(op, body, 'content_session_id');\n    if (!contentSessionId) return null;\n    const platform = normalizePlatformSource(fieldString(op, body, 'platform_source') ?? undefined);\n    const row = this.db.prepare(`\n      SELECT id FROM sdk_sessions\n      WHERE COALESCE(NULLIF(platform_source, ''), ?) = ? AND content_session_id = ?\n      LIMIT 1\n    `).get(DEFAULT_PLATFORM_SOURCE, platform, contentSessionId) as { id: number } | undefined;\n    return row?.id ?? null;\n  }\n\n  private applyPrompt(op: SyncOp, body: Record<string, unknown>, chromaJobs: ChromaJob[]): 'applied' | 'stale' {\n    const contentSessionId = fieldString(op, body, 'content_session_id');\n    const promptNumber = fieldNumber(op, body, 'prompt_number');\n    const promptText = fieldString(op, body, 'prompt_text');\n    const createdAtEpoch = fieldNumber(op, body, 'created_at_epoch');\n    if (!contentSessionId || promptNumber === null || promptText === null || createdAtEpoch === null) {\n      throw invalidOp(op, 'prompt body requires content_session_id, prompt_number, prompt_text, created_at_epoch');\n    }\n    const createdAt = fieldString(op, body, 'created_at') ?? new Date(createdAtEpoch).toISOString();\n\n    const existing = this.findByOrigin('user_prompts', op.origin_device, op.origin_id);\n    if (existing) {\n      if (compareCanonicalDecimals(op.rev, existing.sync_rev) <= 0) return 'stale';\n      const sessionDbId = this.resolvePromptSession(op, body, createdAtEpoch);\n      this.db.prepare(`\n        UPDATE user_prompts SET\n          session_db_id = ?, content_session_id = ?, prompt_number = ?, prompt_text = ?,\n          created_at = ?, created_at_epoch = ?, sync_rev = ?, synced_at = ?\n        WHERE id = ?\n      `).run(\n        sessionDbId, contentSessionId, promptNumber, promptText,\n        createdAt, createdAtEpoch, op.rev, this.now(),\n        existing.id\n      );\n      return 'applied';","sourceCodeStart":875,"sourceCodeEnd":911,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/SyncApply.ts#L875-L911","documentation":"applyPrompt requires content_session_id, prompt_number, prompt_text, and created_at_epoch. user_prompts rows are keyed by their session and prompt ordinal, so a prompt without these cannot be deduplicated, ordered, or linked to its conversation — the op is rejected.","triggerScenarios":"A prompt op body omits content_session_id, has prompt_number null or non-numeric, has empty/null prompt_text, or lacks created_at_epoch.","commonSituations":"A capture hook fired for a prompt before session attribution was resolved; prompt_number was lost when parsing a transcript; prompt_text was empty (e.g. only an attachment was sent); an exporter stripped whitespace-only prompts.","solutions":["Fix the producer to include all four fields: contentSessionId (string), promptNumber (number), promptText (non-empty string), createdAtEpoch (number).","Skip enqueueing prompts with empty prompt_text on the producer instead of sending an empty-string placeholder.","Resolve session attribution before capture: buffer prompts until content_session_id is known.","Validate the payload pre-enqueue to catch missing keys before the op reaches the sync log."],"exampleFix":"// before\nbody: { prompt_text: '  ' }\n// after\nbody: { prompt_text: text.trim(), content_session_id: sessionId, prompt_number: n, created_at_epoch: Date.now() }","handlingStrategy":"validation","validationCode":"function isValidPromptBody(b) {\n  return typeof b.content_session_id === 'string' && b.content_session_id.length > 0\n    && typeof b.prompt_number === 'number' && Number.isFinite(b.prompt_number)\n    && typeof b.prompt_text === 'string' && b.prompt_text.length > 0\n    && typeof b.created_at_epoch === 'number' && Number.isFinite(b.created_at_epoch);\n}","typeGuard":"function hasRequiredPromptFields(b) {\n  return b != null && 'content_session_id' in b && 'prompt_number' in b && 'prompt_text' in b && 'created_at_epoch' in b;\n}","tryCatchPattern":"try {\n  applySyncOp(op);\n} catch (e) {\n  if (String(e.message).includes('prompt body requires')) {\n    deadLetterQueue.push(op); // fix session attribution / trim text and re-emit\n  } else throw e;\n}","preventionTips":["Resolve content_session_id before capturing a prompt; buffer until known","Drop empty/whitespace-only prompts at capture time","Validate prompt payloads against a schema before enqueueing"],"tags":["sync","validation","missing-required-field"],"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"}