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}: set_prompt_session requires target.origin_device_id, target.origin_local_id, fields.memory_session_id What it means
The set_prompt_session apply path requires three pieces of addressing data: target.origin_device_id, target.origin_local_id (to locate the originating device's row, native or replica), and fields.memory_session_id (the value to set). Missing any of the three makes the op unresolvable, so it is rejected with invalidOp before any database access.
Solutions
- Populate all three: target.origin_device_id, target.origin_local_id, fields.memory_session_id
- Verify the producer uses the registered sync deviceId (this.deviceId format), not a hostname
- If memory_session_id was moved to target, restore it under fields to match the current op schema
- Log the offending op at seq=${op.seq} and re-emit it correctly
Example fix
// before
body: { target: { origin_local_id: '42' }, fields: {} }
// after
body: { target: { origin_device_id: 'dev-abc', origin_local_id: '42' }, fields: { memory_session_id: 'mem-123' } } Defensive patterns
Strategy: validation
Validate before calling
function isValidSetPromptSessionBody(body: Record<string, unknown>): boolean {
const t = body.target as any, f = body.fields as any;
return typeof t?.origin_device_id === 'string' &&
typeof t?.origin_local_id === 'string' &&
typeof f?.memory_session_id === 'string';
} Type guard
const isSetPromptSessionBody = (b: unknown): b is { target: { origin_device_id: string; origin_local_id: string }; fields: { memory_session_id: string } } =>
typeof b === 'object' && b !== null &&
typeof (b as any).target?.origin_device_id === 'string' &&
typeof (b as any).target?.origin_local_id === 'string' &&
typeof (b as any).fields?.memory_session_id === 'string'; Try / catch
try { apply(op); } catch (e) {
if (e instanceof InvalidOpError && e.reason.includes('set_prompt_session requires')) {
quarantineOp(e.seq);
} else throw e;
} Prevention
- Use the device's registered sync deviceId, never a hostname or display name
- Keep origin_local_id as the producer-side rowid string
- Centralize op-body builders so all three fields are set together
- Test cross-device replication with real device ids
When it happens
Trigger: A set_prompt_session op arrives missing any of: target.origin_device_id, target.origin_local_id, or fields.memory_session_id — e.g. target points at the device by name instead of deviceId, or fields omits the memory session id.
Common situations: A producer on another device using its device name rather than the sync deviceId; a partially-built op from new tooling; schema drift where memory_session_id moved into target instead of fields.
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=
- canonical content
- device_id must be 1-128 characters
- deviceId must be non-empty
- invalid_ops: deviceId must be 1-128 characters
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/b217aa96b98b3f7b.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/SyncApply.ts:1048
.run(customTitle, viaPrompt.session_db_id);
return 'applied';
}
// (3) Park — claimed when the session (or the content↔memory
// association) shows up. Genuine hub-log order is [set_title, row ops…],
// so this is the COMMON path on a device that has never seen the session.
this.parkTitle(SyncApply.parkedTitleContentKey(platform, contentSessionId), customTitle);
return 'applied';
}
private applySetPromptSession(op: SyncOp, body: Record<string, unknown>): 'applied' | 'stale' {
const target = body.target as Record<string, unknown> | undefined;
const fields = body.fields as Record<string, unknown> | undefined;
const targetDevice = target ? fieldString(op, target, 'origin_device_id') : null;
const targetLocalId = target ? fieldString(op, target, 'origin_local_id') : null;
const memorySessionId = fields ? fieldString(op, fields, 'memory_session_id') : null;
if (!targetDevice || !targetLocalId || !memorySessionId) {
throw invalidOp(op, 'set_prompt_session requires target.origin_device_id, target.origin_local_id, fields.memory_session_id');
}
// Origin identity match: a row that originated HERE is a native row
// (origin columns NULL, origin_local_id is its rowid); anywhere else it
// is a replica keyed by the origin pair.
const row = (targetDevice === this.deviceId
? this.db.prepare(
`SELECT id, CAST(sync_rev AS TEXT) AS sync_rev
FROM user_prompts WHERE origin_device_id IS NULL AND id = ?`
).get(targetLocalId)
: this.db.prepare(
`SELECT id, CAST(sync_rev AS TEXT) AS sync_rev
FROM user_prompts WHERE origin_device_id = ? AND origin_local_id = ?`
).get(targetDevice, targetLocalId)) as RowIdRev | undefined;
if (!row) return 'stale'; // row not replicated here (yet) — nothing to repair
if (compareCanonicalDecimals(op.rev, row.sync_rev) < 0) return 'stale'; // stale mutation
const sessionDbId = this.ensureSessionForMemoryId(View on GitHub (pinned to d8bc9755e7)