thedotmack/claude-mem · error · Error

SyncApply: invalid op seq=

Error message

SyncApply: invalid op seq=${op.seq} kind=${op.kind} origin=${op.origin_device}/${op.origin_id}: summary body requires memory_session_id, project, created_at_epoch

What it means

applySummary requires memory_session_id, project, and created_at_epoch in the summary body. A summary is always attached to a session and project for querying and Chroma indexing; without these the row would be an orphan, so the op is rejected.

Solutions

  1. Fix the producer to attach memory_session_id, project, and a finite created_at_epoch to every summary body.
  2. On the producer, resolve the session before generating the summary; if the session is gone, skip the summary instead of enqueueing it.
  3. Validate summary payloads pre-enqueue (required keys + types) so bad ops never enter the sync log.
  4. If project context is genuinely unknown, backfill a default project label rather than sending an empty string.

Example fix

// before
body: { summary: '...' }
// after
body: { summary: '...', memory_session_id: sessionId, project: 'my-project', created_at_epoch: Date.now() }
Defensive patterns

Strategy: validation

Validate before calling

function isValidSummaryBody(b) {
  return typeof b.memory_session_id === 'string' && b.memory_session_id.length > 0
    && typeof b.project === 'string' && b.project.length > 0
    && typeof b.created_at_epoch === 'number' && Number.isFinite(b.created_at_epoch);
}

Type guard

function hasRequiredSummaryFields(b) {
  return b != null && 'memory_session_id' in b && 'project' in b && 'created_at_epoch' in b;
}

Try / catch

try {
  applySyncOp(op);
} catch (e) {
  if (String(e.message).includes('summary body requires')) {
    deadLetterQueue.push(op); // re-emit with session/project attached
  } else throw e;
}

Prevention

When it happens

Trigger: A summary sync op body lacks memory_session_id or project, sends empty strings, or omits created_at_epoch (null) with no numeric value present.

Common situations: A summarizer job ran after a session was evicted and couldn't resolve its id; a producer refactor renamed memory_session_id; summaries generated offline without project context were synced as-is.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/428bed1de42795ed. Report an issue: GitHub.

Appendix: source

Thrown at src/services/sync/SyncApply.ts:798

          facts: parseListColumn(body.facts),
          narrative: fieldString(op, body, 'narrative'),
          concepts: parseListColumn(body.concepts),
          files_read: parseListColumn(body.files_read),
          files_modified: parseListColumn(body.files_modified),
        },
        fieldNumber(op, body, 'prompt_number') ?? 0,
        createdAtEpoch
      ));
    }
    return 'applied';
  }

  private applySummary(op: SyncOp, body: Record<string, unknown>, chromaJobs: ChromaJob[]): 'applied' | 'stale' {
    const memorySessionId = fieldString(op, body, 'memory_session_id');
    const project = fieldString(op, body, 'project');
    const createdAtEpoch = fieldNumber(op, body, 'created_at_epoch');
    if (!memorySessionId || !project || createdAtEpoch === null) {
      throw invalidOp(op, 'summary body requires memory_session_id, project, created_at_epoch');
    }
    const createdAt = fieldString(op, body, 'created_at') ?? new Date(createdAtEpoch).toISOString();

    const existing = this.findByOrigin('session_summaries', op.origin_device, op.origin_id);
    if (existing) {
      if (compareCanonicalDecimals(op.rev, existing.sync_rev) <= 0) return 'stale';
      this.ensureSessionForMemoryId(memorySessionId, project, createdAtEpoch); // FK holds even if the body re-homed the row
      this.db.prepare(`
        UPDATE session_summaries SET
          memory_session_id = ?, project = ?, request = ?, investigated = ?, learned = ?, completed = ?,
          next_steps = ?, files_read = ?, files_edited = ?, notes = ?, prompt_number = ?,
          discovery_tokens = ?, merged_into_project = ?, created_at = ?, created_at_epoch = ?,
          sync_rev = ?, synced_at = ?
        WHERE id = ?
      `).run(
        memorySessionId, project, fieldString(op, body, 'request'), fieldString(op, body, 'investigated'), fieldString(op, body, 'learned'), fieldString(op, body, 'completed'),
        fieldString(op, body, 'next_steps'), fieldString(op, body, 'files_read'), fieldString(op, body, 'files_edited'), fieldString(op, body, 'notes'), fieldNumber(op, body, 'prompt_number'),
        fieldNumber(op, body, 'discovery_tokens') ?? 0, fieldString(op, body, 'merged_into_project'), createdAt, createdAtEpoch,

View on GitHub (pinned to d8bc9755e7)