thedotmack/claude-mem · error

SyncApply: could not create or adopt a session for…

Error message

SyncApply: could not create or adopt a session for memory_session_id=${memorySessionId}

What it means

Thrown by SyncApply.ensureSdkSession after an INSERT ... ON CONFLICT(platform_source, content_session_id) DO NOTHING RETURNING id returned no row (meaning a conflicting row exists) while the follow-up SELECT by that same (platform_source, content_session_id) identity found nothing. That combination should be impossible on a well-formed schema: the row the insert conflicted with cannot be found again. It indicates schema drift (the migration v33 unique index missing or shaped differently), a concurrent writer on another connection deleting the session mid-batch, or platform normalization mismatch between the write and read paths.

Solutions

  1. Run the pending schema migrations so the v33 ON CONFLICT(platform_source, content_session_id) unique index actually exists.
  2. Ensure exactly one process runs SyncApply against the database; stop duplicate workers/supervisors.
  3. Inspect sdk_sessions for the memory_session_id and its (platform_source, content_session_id) neighbors to see what the insert conflicted with.
  4. As a last resort rebuild the local DB and re-sync from scratch — apply is idempotent.
Defensive patterns

Strategy: try-catch

Validate before calling

// Before enabling sync, confirm the v33 unique index exists so ON CONFLICT adopts correctly.
const idx = db.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='sdk_sessions'").all();
if (!idx.some(i => /platform_source.*content_session_id|content_session_id.*platform_source/.test(String(i.name)))) {
  throw new Error('sdk_sessions unique index missing — run migrations before syncing');
}

Try / catch

try {
  syncApply.applyOps(ops, { epoch, requireContiguous: true });
} catch (e) {
  if (e instanceof Error && e.message.includes('could not create or adopt a session')) {
  // Schema/writer conflict: stop syncing rather than lose session-linked ops.
    logger.error('SYNC', 'Halting sync: sdk_sessions adoption failed — check migrations and single-writer invariant', {}, e);
    stopSyncLoop();
  } else throw e;
}

Prevention

When it happens

Trigger: Applying a remote observation/memory op whose memory_session_id is unknown locally, while the sdk_sessions table lacks the expected (platform_source, content_session_id) unique index (old DB never migrated), or a second process (e.g. supervisor + worker both syncing) deletes/rewrites sdk_sessions between the INSERT and SELECT.

Common situations: Upgrading claude-mem with a pre-v33 database where migrations were skipped or partially applied; running two components that both run SyncApply against the same SQLite file; a manual DB cleanup script deleting sessions while sync runs.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/7e63e380169ffe7f. Report an issue: GitHub.

Appendix: source

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

        (content_session_id, memory_session_id, project, platform_source, user_prompt, started_at, started_at_epoch, status)
      VALUES (?, ?, ?, ?, NULL, ?, ?, 'completed')
      ON CONFLICT(platform_source, content_session_id) DO NOTHING
      RETURNING id
    `).get(content, memorySessionId, project, platform, iso, createdAtEpoch) as { id: number } | null;

    let sessionId: number;
    if (inserted) {
      logger.debug('SYNC_APPLY', 'Created stub sdk_session for remote memory session', {
        memorySessionId,
        project,
      });
      sessionId = inserted.id;
    } else {
      const adopted = this.db.prepare(
        'SELECT id FROM sdk_sessions WHERE platform_source = ? AND content_session_id = ?'
      ).get(platform, content) as { id: number } | undefined;
      if (!adopted) {
        throw new Error(`SyncApply: could not create or adopt a session for memory_session_id=${memorySessionId}`);
      }
      sessionId = adopted.id;
    }

    this.claimParkedTitle(sessionId, SyncApply.parkedTitleMemKey(memorySessionId));
    if (contentSessionId) {
      this.claimParkedTitle(sessionId, SyncApply.parkedTitleContentKey(platform, contentSessionId));
    }
    return sessionId;
  }

  private applyObservation(op: SyncOp, body: Record<string, unknown>, chromaJobs: ChromaJob[]): 'applied' | 'stale' {
    const memorySessionId = fieldString(op, body, 'memory_session_id');
    const project = fieldString(op, body, 'project');
    const type = fieldString(op, body, 'type');
    const createdAtEpoch = fieldNumber(op, body, 'created_at_epoch');
    if (!memorySessionId || !project || !type || createdAtEpoch === null) {
      throw invalidOp(op, 'observation body requires memory_session_id, project, type, created_at_epoch');

View on GitHub (pinned to e2d1df569a)