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

  1. 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.).
  2. Upgrade this node to a version that recognizes the new mutation op if it was introduced by a newer producer.
  3. Add an explicit migration for in-flight ops when renaming a mutation kind: rewrite queued rows to the new op name before deploying.
  4. 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

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


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)