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
- Fix the producer to attach memory_session_id, project, and a finite created_at_epoch to every summary body.
- On the producer, resolve the session before generating the summary; if the session is gone, skip the summary instead of enqueueing it.
- Validate summary payloads pre-enqueue (required keys + types) so bad ops never enter the sync log.
- 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
- Generate summaries only while the session context is still resolvable
- Validate summary payloads pre-enqueue
- Backfill a project label instead of syncing without one
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
- SyncApply: invalid op seq=
- SyncApply: invalid op seq=
- canonical content
- device_id must be 1-128 characters
- deviceId must be non-empty
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)