thedotmack/claude-mem · error

Session not found in sdk_sessions

Error message

Session ${sessionDbId} not found in sdk_sessions

What it means

SessionStore looks up a session by its numeric database id in sdk_sessions to obtain memory_session_id and worker_port. When no row matches, it throws 'Session <id> not found in sdk_sessions', indicating the caller referenced a session that does not exist (deleted, wrong id, or never registered).

Solutions

  1. Verify the id exists: SELECT id, memory_session_id FROM sdk_sessions WHERE id = ?
  2. Ensure you pass the numeric DB id (sdk_sessions.id), not a memory/session UUID
  3. Re-register the session if it was deleted before retrying the operation
  4. Handle the throw: treat a missing session as 'recreate from source' in worker code

Example fix

// before
store.getWorkerPort(staleSessionId); // throws
// after
const exists = store.findSession(staleSessionId);
if (!exists) session = store.registerSession(newSessionId);
Defensive patterns

Strategy: try-catch

Validate before calling

const row = db.prepare('SELECT id FROM sdk_sessions WHERE id = ?').get(sessionDbId);
if (!row) throw new Error(`Skip: session ${sessionDbId} no longer exists`);

Type guard

function sessionExists(db: Database, sessionDbId: number): boolean {
  return !!db.prepare('SELECT 1 FROM sdk_sessions WHERE id = ?').get(sessionDbId);
}

Try / catch

try { info = store.getSessionWorkerInfo(sessionDbId); } catch (e) {
  if (String(e.message).startsWith('Session ') && String(e.message).endsWith('not found in sdk_sessions')) {
    info = registerNewSession(); // stale id — recreate
  } else throw e;
}

Prevention

When it happens

Trigger: Calling SessionStore methods (e.g. the public method at src/services/sqlite/SessionStore.ts:2291) with a sessionDbId that is not present in sdk_sessions — a stale/hard-deleted session id, a typo'd id, or querying before the session was registered.

Common situations: Workers or hooks retaining a session id across a database reset; resuming from persisted state whose session was pruned; passing a Claude/session UUID instead of the numeric DB id.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/services/sqlite/SessionStore.ts:2291

    const nowIso = new Date(nowEpoch).toISOString();
    this.db.prepare(`
      UPDATE sdk_sessions
      SET status = 'completed', completed_at = ?, completed_at_epoch = ?
      WHERE id = ?
    `).run(nowIso, nowEpoch, sessionDbId);
  }

  ensureMemorySessionIdRegistered(
    sessionDbId: number,
    memorySessionId: string,
    workerPort?: number
  ): string {
    const session = this.db.prepare(`
      SELECT id, memory_session_id, worker_port FROM sdk_sessions WHERE id = ?
    `).get(sessionDbId) as { id: number; memory_session_id: string | null; worker_port: number | null } | undefined;

    if (!session) {
      throw new Error(`Session ${sessionDbId} not found in sdk_sessions`);
    }

    // REGISTER, DO NOT RE-REGISTER. `memory_session_id` is the FK parent key of
    // `observations` and `session_summaries` (ON UPDATE CASCADE) and the join
    // field `requeuePromptSync` pushes to replicas, so overwriting it is not a
    // field update — it rewrites every memory the session owns and re-enqueues
    // every prompt it has.
    //
    // The caller that made this matter is ClaudeProvider: a fresh SDK process
    // mints a new session_id every turn, `resetCarriedMemorySessionId` clears the
    // in-memory copy before each one, and nothing consumes a later turn's id
    // (`shouldResume` is a hardcoded false, so `resume` never receives it). The
    // condition below used to be `!==`, so every turn looked like a new identity.
    //
    // MEASURED on one store: sync_outbox held 1,100,783 rows for 6,930 distinct
    // prompts — 158.8x, 393 MB of an 854 MB database — with its worst single
    // prompt carrying 3,464 rows and 3,464 DISTINCT memory_session_ids. That is
    // `requeuePromptSync`, whose own docstring describes a one-time repair

View on GitHub (pinned to d8bc9755e7)