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}: prompt body requires content_session_id, prompt_number, prompt_text, created_at_epoch

What it means

applyPrompt requires content_session_id, prompt_number, prompt_text, and created_at_epoch. user_prompts rows are keyed by their session and prompt ordinal, so a prompt without these cannot be deduplicated, ordered, or linked to its conversation — the op is rejected.

Solutions

  1. Fix the producer to include all four fields: contentSessionId (string), promptNumber (number), promptText (non-empty string), createdAtEpoch (number).
  2. Skip enqueueing prompts with empty prompt_text on the producer instead of sending an empty-string placeholder.
  3. Resolve session attribution before capture: buffer prompts until content_session_id is known.
  4. Validate the payload pre-enqueue to catch missing keys before the op reaches the sync log.

Example fix

// before
body: { prompt_text: '  ' }
// after
body: { prompt_text: text.trim(), content_session_id: sessionId, prompt_number: n, created_at_epoch: Date.now() }
Defensive patterns

Strategy: validation

Validate before calling

function isValidPromptBody(b) {
  return typeof b.content_session_id === 'string' && b.content_session_id.length > 0
    && typeof b.prompt_number === 'number' && Number.isFinite(b.prompt_number)
    && typeof b.prompt_text === 'string' && b.prompt_text.length > 0
    && typeof b.created_at_epoch === 'number' && Number.isFinite(b.created_at_epoch);
}

Type guard

function hasRequiredPromptFields(b) {
  return b != null && 'content_session_id' in b && 'prompt_number' in b && 'prompt_text' in b && 'created_at_epoch' in b;
}

Try / catch

try {
  applySyncOp(op);
} catch (e) {
  if (String(e.message).includes('prompt body requires')) {
    deadLetterQueue.push(op); // fix session attribution / trim text and re-emit
  } else throw e;
}

Prevention

When it happens

Trigger: A prompt op body omits content_session_id, has prompt_number null or non-numeric, has empty/null prompt_text, or lacks created_at_epoch.

Common situations: A capture hook fired for a prompt before session attribution was resolved; prompt_number was lost when parsing a transcript; prompt_text was empty (e.g. only an attachment was sent); an exporter stripped whitespace-only prompts.

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

Appendix: source

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

    // it once the origin registers).
    const contentSessionId = fieldString(op, body, 'content_session_id');
    if (!contentSessionId) return null;
    const platform = normalizePlatformSource(fieldString(op, body, 'platform_source') ?? undefined);
    const row = this.db.prepare(`
      SELECT id FROM sdk_sessions
      WHERE COALESCE(NULLIF(platform_source, ''), ?) = ? AND content_session_id = ?
      LIMIT 1
    `).get(DEFAULT_PLATFORM_SOURCE, platform, contentSessionId) as { id: number } | undefined;
    return row?.id ?? null;
  }

  private applyPrompt(op: SyncOp, body: Record<string, unknown>, chromaJobs: ChromaJob[]): 'applied' | 'stale' {
    const contentSessionId = fieldString(op, body, 'content_session_id');
    const promptNumber = fieldNumber(op, body, 'prompt_number');
    const promptText = fieldString(op, body, 'prompt_text');
    const createdAtEpoch = fieldNumber(op, body, 'created_at_epoch');
    if (!contentSessionId || promptNumber === null || promptText === null || createdAtEpoch === null) {
      throw invalidOp(op, 'prompt body requires content_session_id, prompt_number, prompt_text, created_at_epoch');
    }
    const createdAt = fieldString(op, body, 'created_at') ?? new Date(createdAtEpoch).toISOString();

    const existing = this.findByOrigin('user_prompts', op.origin_device, op.origin_id);
    if (existing) {
      if (compareCanonicalDecimals(op.rev, existing.sync_rev) <= 0) return 'stale';
      const sessionDbId = this.resolvePromptSession(op, body, createdAtEpoch);
      this.db.prepare(`
        UPDATE user_prompts SET
          session_db_id = ?, content_session_id = ?, prompt_number = ?, prompt_text = ?,
          created_at = ?, created_at_epoch = ?, sync_rev = ?, synced_at = ?
        WHERE id = ?
      `).run(
        sessionDbId, contentSessionId, promptNumber, promptText,
        createdAt, createdAtEpoch, op.rev, this.now(),
        existing.id
      );
      return 'applied';

View on GitHub (pinned to d8bc9755e7)