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
- Verify the id exists: SELECT id, memory_session_id FROM sdk_sessions WHERE id = ?
- Ensure you pass the numeric DB id (sdk_sessions.id), not a memory/session UUID
- Re-register the session if it was deleted before retrying the operation
- 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
- Persist the numeric sdk_sessions.id, not a UUID, in worker state
- Purge worker/job state when the database is reset
- Check session existence before enqueuing background work for it
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
- Session not found
- Cannot process observations: memorySessionId not yet…
- Cannot process summary: memorySessionId not yet captured…
- canonical content: exceeds the local SQLite safe-integer…
- cloud sync canonical payload
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 repairView on GitHub (pinned to d8bc9755e7)