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}: observation body requires memory_session_id, project, type, created_at_epoch What it means
applyObservation requires the four columns the observations table treats as NOT NULL/essential: memory_session_id, project, type, and created_at_epoch. If any is missing, null, or empty (and no fallback can fill it), the op is rejected rather than inserting an orphan observation that downstream queries and Chroma embedding jobs can't associate.
Solutions
- Fix the producer to include memory_session_id, project, type, and created_at_epoch (a number) in every observation body.
- Provide created_at (ISO string) as a fallback only if created_at_epoch must be derived — but note created_at_epoch itself is still required.
- Reject or queue such observations on the producer side before enqueueing, validating required keys.
- If the context genuinely doesn't exist yet, defer creating the observation until the session/project is known.
Example fix
// before
body: { type: 'note', content: '...' }
// after
body: { type: 'note', content: '...', memory_session_id: sessionId, project: 'my-project', created_at_epoch: Date.now() } Defensive patterns
Strategy: validation
Validate before calling
function isValidObservationBody(b) {
return typeof b.memory_session_id === 'string' && b.memory_session_id.length > 0
&& typeof b.project === 'string' && b.project.length > 0
&& typeof b.type === 'string' && b.type.length > 0
&& typeof b.created_at_epoch === 'number' && Number.isFinite(b.created_at_epoch);
} Type guard
function hasRequiredObservationFields(b) {
return b != null && 'memory_session_id' in b && 'project' in b && 'type' in b && 'created_at_epoch' in b;
} Try / catch
try {
applySyncOp(op);
} catch (e) {
if (String(e.message).includes('observation body requires')) {
deadLetterQueue.push(op); // backfill required fields and re-emit
} else throw e;
} Prevention
- Validate observation payloads against a schema before enqueueing
- Never start an observation before session/project context is resolved
- Treat created_at_epoch as mandatory even when created_at is provided
When it happens
Trigger: An observation sync op body omits one of the required keys, sends them as null/empty strings, or sends created_at_epoch as null while also omitting created_at.
Common situations: A producer that records observations before a session/project context is established sends empty project; a schema change renamed created_at_epoch on the writer; a partial serialization skipped optional-looking fields that are actually required.
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/abaab51d1a833145.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/SyncApply.ts:711
throw new Error(`SyncApply: could not create or adopt a session for memory_session_id=${memorySessionId}`);
}
sessionId = adopted.id;
}
this.claimParkedTitle(sessionId, SyncApply.parkedTitleMemKey(memorySessionId));
if (contentSessionId) {
this.claimParkedTitle(sessionId, SyncApply.parkedTitleContentKey(platform, contentSessionId));
}
return sessionId;
}
private applyObservation(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 type = fieldString(op, body, 'type');
const createdAtEpoch = fieldNumber(op, body, 'created_at_epoch');
if (!memorySessionId || !project || !type || createdAtEpoch === null) {
throw invalidOp(op, 'observation body requires memory_session_id, project, type, created_at_epoch');
}
const createdAt = fieldString(op, body, 'created_at') ?? new Date(createdAtEpoch).toISOString();
const existing = this.findByOrigin('observations', 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 observations SET
memory_session_id = ?, project = ?, text = ?, type = ?, title = ?, subtitle = ?,
facts = ?, narrative = ?, concepts = ?, files_read = ?, files_modified = ?,
prompt_number = ?, discovery_tokens = ?, content_hash = ?, generated_by_model = ?,
agent_type = ?, agent_id = ?, metadata = ?, merged_into_project = ?,
created_at = ?, created_at_epoch = ?, sync_rev = ?, synced_at = ?
WHERE id = ?
`).run(
memorySessionId, project, fieldString(op, body, 'text'), type, fieldString(op, body, 'title'), fieldString(op, body, 'subtitle'),
fieldString(op, body, 'facts'), fieldString(op, body, 'narrative'), fieldString(op, body, 'concepts'), fieldString(op, body, 'files_read'), fieldString(op, body, 'files_modified'),View on GitHub (pinned to d8bc9755e7)