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_prompt_session requires target.origin_device_id, target.origin_local_id, fields.memory_session_id

What it means

The set_prompt_session apply path requires three pieces of addressing data: target.origin_device_id, target.origin_local_id (to locate the originating device's row, native or replica), and fields.memory_session_id (the value to set). Missing any of the three makes the op unresolvable, so it is rejected with invalidOp before any database access.

Solutions

  1. Populate all three: target.origin_device_id, target.origin_local_id, fields.memory_session_id
  2. Verify the producer uses the registered sync deviceId (this.deviceId format), not a hostname
  3. If memory_session_id was moved to target, restore it under fields to match the current op schema
  4. Log the offending op at seq=${op.seq} and re-emit it correctly

Example fix

// before
body: { target: { origin_local_id: '42' }, fields: {} }
// after
body: { target: { origin_device_id: 'dev-abc', origin_local_id: '42' }, fields: { memory_session_id: 'mem-123' } }
Defensive patterns

Strategy: validation

Validate before calling

function isValidSetPromptSessionBody(body: Record<string, unknown>): boolean {
  const t = body.target as any, f = body.fields as any;
  return typeof t?.origin_device_id === 'string' &&
         typeof t?.origin_local_id === 'string' &&
         typeof f?.memory_session_id === 'string';
}

Type guard

const isSetPromptSessionBody = (b: unknown): b is { target: { origin_device_id: string; origin_local_id: string }; fields: { memory_session_id: string } } =>
  typeof b === 'object' && b !== null &&
  typeof (b as any).target?.origin_device_id === 'string' &&
  typeof (b as any).target?.origin_local_id === 'string' &&
  typeof (b as any).fields?.memory_session_id === 'string';

Try / catch

try { apply(op); } catch (e) {
  if (e instanceof InvalidOpError && e.reason.includes('set_prompt_session requires')) {
    quarantineOp(e.seq);
  } else throw e;
}

Prevention

When it happens

Trigger: A set_prompt_session op arrives missing any of: target.origin_device_id, target.origin_local_id, or fields.memory_session_id — e.g. target points at the device by name instead of deviceId, or fields omits the memory session id.

Common situations: A producer on another device using its device name rather than the sync deviceId; a partially-built op from new tooling; schema drift where memory_session_id moved into target instead of fields.

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

Appendix: source

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

        .run(customTitle, viaPrompt.session_db_id);
      return 'applied';
    }

    // (3) Park — claimed when the session (or the content↔memory
    // association) shows up. Genuine hub-log order is [set_title, row ops…],
    // so this is the COMMON path on a device that has never seen the session.
    this.parkTitle(SyncApply.parkedTitleContentKey(platform, contentSessionId), customTitle);
    return 'applied';
  }

  private applySetPromptSession(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;
    const targetDevice = target ? fieldString(op, target, 'origin_device_id') : null;
    const targetLocalId = target ? fieldString(op, target, 'origin_local_id') : null;
    const memorySessionId = fields ? fieldString(op, fields, 'memory_session_id') : null;
    if (!targetDevice || !targetLocalId || !memorySessionId) {
      throw invalidOp(op, 'set_prompt_session requires target.origin_device_id, target.origin_local_id, fields.memory_session_id');
    }

    // Origin identity match: a row that originated HERE is a native row
    // (origin columns NULL, origin_local_id is its rowid); anywhere else it
    // is a replica keyed by the origin pair.
    const row = (targetDevice === this.deviceId
      ? this.db.prepare(
          `SELECT id, CAST(sync_rev AS TEXT) AS sync_rev
           FROM user_prompts WHERE origin_device_id IS NULL AND id = ?`
        ).get(targetLocalId)
      : this.db.prepare(
          `SELECT id, CAST(sync_rev AS TEXT) AS sync_rev
           FROM user_prompts WHERE origin_device_id = ? AND origin_local_id = ?`
        ).get(targetDevice, targetLocalId)) as RowIdRev | undefined;
    if (!row) return 'stale'; // row not replicated here (yet) — nothing to repair
    if (compareCanonicalDecimals(op.rev, row.sync_rev) < 0) return 'stale'; // stale mutation

    const sessionDbId = this.ensureSessionForMemoryId(

View on GitHub (pinned to d8bc9755e7)