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}: remap_project requires where and fields objects

What it means

applyRemapProject performs a targeted UPDATE of project/merged_into_project columns. It requires both body.where (the row selector) and body.fields (the column assignments) to be plain objects; either being absent or a non-object makes the op meaningless and it is rejected via invalidOp before any SQL is built.

Solutions

  1. Emit body.where and body.fields as plain objects in every remap_project op
  2. Fix producer serialization so fields is an object mapping column names to values
  3. Validate the op body on the producer before appending to the sync log

Example fix

// before
body: { fields: 'project=proj-b' }
// after
body: { where: { project: 'proj-a' }, fields: { project: 'proj-b' } }
Defensive patterns

Strategy: validation

Validate before calling

function isValidRemapBody(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.where) && isObj(b.fields);
}
if (!isValidRemapBody(op.body)) throw new Error('remap_project needs where and fields objects');

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('remap_project requires where and fields')) {
    quarantineOp(e.seq);
  } else throw e;
}

Prevention

When it happens

Trigger: A remap_project op arrives with body.where or body.fields missing, null, an array, or a primitive (e.g. fields sent as a flat string 'project=newproj').

Common situations: A producer serializing the remap payload incorrectly; an op built by an older tool version with a different body layout; manual log editing or test fixtures with an abbreviated shape.

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/64a35dabb33e03b5. Report an issue: GitHub.

Appendix: source

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

      memorySessionId,
      (fields && fieldString(op, fields, 'project')) ?? 'unknown',
      op.server_ts,
      fields ? fieldString(op, fields, 'content_session_id') : null,
      fields ? fieldString(op, fields, 'platform_source') : null
    );
    // synced_at deliberately untouched: the repair travels through the log
    // itself, so applying it must not queue a re-push (echo guard).
    this.db.prepare(
      'UPDATE user_prompts SET session_db_id = ?, sync_rev = ? WHERE id = ?'
    ).run(sessionDbId, op.rev, row.id);
    return 'applied';
  }

  private applyRemapProject(op: SyncOp, body: Record<string, unknown>): 'applied' | 'stale' {
    const where = body.where as Record<string, unknown> | undefined;
    const fields = body.fields as Record<string, unknown> | undefined;
    if (!where || typeof where !== 'object' || !fields || typeof fields !== 'object') {
      throw invalidOp(op, 'remap_project requires where and fields objects');
    }

    const setClauses: string[] = [];
    const setParams: (string | number)[] = [];
    const newProject = fieldString(op, fields, 'project');
    const mergedInto = fieldString(op, fields, 'merged_into_project');
    if (newProject !== null) {
      setClauses.push('project = ?');
      setParams.push(newProject);
    }
    if (mergedInto !== null) {
      setClauses.push('merged_into_project = ?');
      setParams.push(mergedInto);
    }
    if (setClauses.length === 0) {
      throw invalidOp(op, 'remap_project fields must set project and/or merged_into_project');
    }

View on GitHub (pinned to d8bc9755e7)