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 requires target and fields objects

What it means

SyncApply validates every incoming sync operation before applying it. For ops of kind 'set_title', both body.target and body.fields must be present as plain (non-array) objects; otherwise the op is rejected as invalid and thrown so it is not silently applied or parked. This protects the replica database from malformed or truncated mutation payloads.

Solutions

  1. Inspect the op body in the sync log at seq=${op.seq} and confirm target/fields are emitted as plain objects
  2. Fix the producer so set_title ops always include body.target={...} and body.fields={custom_title:...}
  3. Add validation on the producer side before appending the op to the sync log
  4. If the op is corrupt and unreproducible, drop/quarantine that op from the log so apply can proceed

Example fix

// before
const op = { kind: 'set_title', body: { target: { memory_session_id: 'm1' } } };
// after
const op = { kind: 'set_title', body: { target: { memory_session_id: 'm1' }, fields: { custom_title: 'My chat' } } };
Defensive patterns

Strategy: validation

Validate before calling

function isValidSetTitleBody(body: unknown): boolean {
  if (!body || typeof body !== 'object') return false;
  const b = body as Record<string, unknown>;
  const isObj = (v: unknown) => !!v && typeof v === 'object' && !Array.isArray(v);
  return isObj(b.target) && isObj(b.fields);
}
if (!isValidSetTitleBody(op.body)) throw new Error('set_title op missing target/fields');

Type guard

const isPlainObject = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v);

Try / catch

try { apply(op); } catch (e) {
  if (e instanceof InvalidOpError && e.reason.includes('set_title requires target and fields')) {
    quarantineOp(e.seq); // drop or re-emit corrected op
  } else throw e;
}

Prevention

When it happens

Trigger: A set_title sync op arrives whose body lacks target or fields, or where either is an array, a string, or null instead of a plain object (e.g. a producer sent fields as a JSON list or omitted them).

Common situations: An older or third-party sync producer emitting an out-of-date op schema; a hand-crafted or test op JSON missing the fields object; a serialization bug that turns the fields object into an array; mid-flight schema changes between app versions syncing to the same log.

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


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/8f90843e0e4476a7. Report an issue: GitHub.

Appendix: source

Thrown at src/services/sync/SyncApply.ts:973

        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');
    if (customTitle === null) {
      throw invalidOp(op, 'set_title requires fields.custom_title');
    }

    // NOTE: every branch here is SELECT-then-UPDATE-by-id, never a decision
    // on `.run().changes` — bun:sqlite (1.3.9) has been observed reporting a
    // bogus non-zero `changes` for a zero-match UPDATE when it follows a
    // RETURNING statement inside the same transaction on this connection
    // (reported 4 with a verified 0 matching rows), which silently misroutes
    // 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) {

View on GitHub (pinned to d8bc9755e7)