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}: unknown mutation op ${String(body.op)} What it means
applyMutation dispatches on body.op for mutation ops (set_title, set_prompt_session, remap_project, ...). An unrecognized body.op means the producer is either buggy or from a newer version with mutations this applier can't execute. Rejecting avoids silently ignoring a mutation and diverging replicas.
Solutions
- Inspect body.op for seq=${op.seq}; correct the producer to emit one of the supported mutation ops (set_title, set_prompt_session, remap_project, etc.).
- Upgrade this node to a version that recognizes the new mutation op if it was introduced by a newer producer.
- Add an explicit migration for in-flight ops when renaming a mutation kind: rewrite queued rows to the new op name before deploying.
- Fail fast on the producer: validate the op name against the supported set at enqueue time.
Example fix
// before
body: { op: 'rename_title', ... }
// after
body: { op: 'set_title', ... } Defensive patterns
Strategy: try-catch
Validate before calling
const MUTATION_OPS = ['set_title','set_prompt_session','remap_project'];
if (!MUTATION_OPS.includes(body.op)) throw new Error(`unsupported mutation: ${body.op}`); Type guard
const isKnownMutationOp = (op) => ['set_title','set_prompt_session','remap_project'].includes(op);
Try / catch
try {
applySyncOp(op);
} catch (e) {
if (String(e.message).includes('unknown mutation op')) {
logger.warn({ seq: op.seq, mutation: body.op }, 'unknown mutation — upgrade required');
holdQueue.push(op);
} else throw e;
} Prevention
- Share the mutation-op constant list between producer and applier
- Validate the mutation name at enqueue time on the producer
- When renaming a mutation op, migrate queued rows before deploying
When it happens
Trigger: A mutation op body has an op field with an unknown/misspelled value (e.g. 'rename_title' instead of 'set_title'), op is undefined, or a newer producer version introduced a mutation kind this build predates.
Common situations: Version skew after an upgrade that added a new mutation type; a typo in the producer's mutation constant; a refactor renamed mutation ops without a compatibility shim for in-flight queued ops.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- mutation body is not JSON
- mutation body must be an object
- SyncApply: invalid op seq=
- Backfill failed
- canonical content
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/4c7b398d91f3eaa9.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/SyncApply.ts:957
return 'applied';
}
// -------------------------------------------------------------------------
// Mutation ops
// -------------------------------------------------------------------------
private applyMutation(op: SyncOp): 'applied' | 'stale' {
const body = this.parseBody(op);
const mutationOp = fieldString(op, body, 'op');
switch (mutationOp) {
case 'set_title':
return this.applySetTitle(op, body);
case 'set_prompt_session':
return this.applySetPromptSession(op, body);
case 'remap_project':
return this.applyRemapProject(op, body);
default:
throw invalidOp(op, `unknown mutation op ${String(body.op)}`);
}
}
/**
* set_title resolution (three steps — see SET_TITLE CONVERGENCE in the
* module header): direct session match → via a replicated prompt's content
* id → park in sync_state for a later claim. sdk_sessions carries no
* sync_rev (v41 touches only the synced tables), so rev is never
* consulted: direct applications overwrite in hub-log order; parked
* claims fill only a NULL custom_title.
*/
private applySetTitle(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;
if (!target || typeof target !== 'object' || Array.isArray(target) || !fields || typeof fields !== 'object' || Array.isArray(fields)) {
throw invalidOp(op, 'set_title requires target and fields objects');
}
const customTitle = fieldString(op, fields, 'custom_title');View on GitHub (pinned to d8bc9755e7)