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_title target requires memory_session_id or content_session_id What it means
applySetTitle needs to know which session row to update. It first tries target.memory_session_id; if absent, it falls back to target.content_session_id. When neither identifier is present the op has no addressable target, so invalidOp is thrown rather than attempting a scoped or unbounded update.
Solutions
- Include target.memory_session_id (preferred) or target.content_session_id in every set_title op
- Confirm the producer resolves the session id before enqueueing the op
- Check for op producers using a stale key name and update them to the supported identifiers
Example fix
// before
target: { platform_source: 'claude-code' }
// after
target: { memory_session_id: 'mem-123', platform_source: 'claude-code' } Defensive patterns
Strategy: validation
Validate before calling
function hasSessionTarget(target: Record<string, unknown>): boolean {
return typeof target.memory_session_id === 'string' || typeof target.content_session_id === 'string';
}
if (!hasSessionTarget(op.body.target)) throw new Error('set_title target needs memory_session_id or content_session_id'); Type guard
const hasSessionId = (t: unknown): t is { memory_session_id?: string; content_session_id?: string } & Record<string, unknown> =>
typeof t === 'object' && t !== null && ('memory_session_id' in t || 'content_session_id' in t); Try / catch
try { apply(op); } catch (e) {
if (e instanceof InvalidOpError && e.reason.includes('memory_session_id or content_session_id')) {
// re-resolve the session id and re-emit the op
} else throw e;
} Prevention
- Resolve the session id on the producer before building the op
- Keep target key names in one shared constants module for producer and consumer
- Add an integration test that round-trips set_title ops through the sync log
When it happens
Trigger: A set_title op whose target object has neither memory_session_id nor content_session_id — e.g. target only carries platform_source, or is an empty object.
Common situations: A producer that targets sessions by a different key (session_uuid, external id) after a schema change; a hand-built op in tooling/tests that omitted the id; a truncation bug dropping the id field.
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/c35081f12c1f58d2.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/SyncApply.ts:1003
// apply-vs-park. SELECTs in our transaction are authoritative.
const memorySessionId = fieldString(op, target, 'memory_session_id');
if (memorySessionId) {
const session = this.db.prepare(
'SELECT id FROM sdk_sessions WHERE memory_session_id = ?'
).get(memorySessionId) as { id: number } | undefined;
if (session) {
this.db.prepare('UPDATE sdk_sessions SET custom_title = ? WHERE id = ?')
.run(customTitle, session.id);
return 'applied';
}
// No such session yet — park until ensureSessionForMemoryId creates it.
this.parkTitle(SyncApply.parkedTitleMemKey(memorySessionId), customTitle);
return 'applied';
}
const contentSessionId = fieldString(op, target, 'content_session_id');
if (!contentSessionId) {
throw invalidOp(op, 'set_title target requires memory_session_id or content_session_id');
}
const platform = normalizePlatformSource(fieldString(op, target, 'platform_source') ?? undefined);
// (1) Direct match on the session identity.
const direct = this.db.prepare(`
SELECT id FROM sdk_sessions
WHERE COALESCE(NULLIF(platform_source, ''), ?) = ? AND content_session_id = ?
LIMIT 1
`).get(DEFAULT_PLATFORM_SOURCE, platform, contentSessionId) as { id: number } | undefined;
if (direct) {
this.db.prepare('UPDATE sdk_sessions SET custom_title = ? WHERE id = ?')
.run(customTitle, direct.id);
return 'applied';
}
// (2) Via a replicated prompt: when row ops arrived first, the
// observation-created stub carries a synthetic content id, but any
// replicated prompt knows its real content_session_id AND its localView on GitHub (pinned to d8bc9755e7)