thedotmack/claude-mem · error

storeObservations: ON CONFLICT without existing row for…

Error message

storeObservations: ON CONFLICT without existing row for content_hash=${contentHash}

What it means

storeObservations performs an INSERT ... ON CONFLICT(content_hash) DO UPDATE/DO NOTHING style upsert and then, for rows that conflicted, re-looks-up the existing row by (memorySessionId, contentHash) to collect its id. If the conflict path was taken but the lookup finds no row, an internal invariant of the upsert is broken, so it throws.

Solutions

  1. Retry the whole storeObservations call; the transient race usually resolves on a second attempt.
  2. Serialize writes with a single writer connection/busy_timeout so concurrent upserts cannot interleave.
  3. Verify the (memory_session_id, content_hash) unique index still exists and matches the ON CONFLICT clause.
  4. Check for code that deletes observation rows concurrently (pruning/cleanup jobs) and exclude the session being written.
Defensive patterns

Strategy: retry

Try / catch

try {
  ids = store.storeObservations(sessionId, project, observations);
} catch (e) {
  if (String(e.message).includes('ON CONFLICT without existing row')) {
    ids = retryWithBackoff(() => store.storeObservations(sessionId, project, observations));
  } else throw e;
}

Prevention

When it happens

Trigger: Concurrent writers inserting the same content_hash into the same memory session between the conflicting INSERT and the lookupExistingStmt.get() re-read, or a deleted row / wrong session id making the lookup miss, or a manually corrupted dedupe index.

Common situations: Two processes (e.g. two Claude-mem workers or a CLI plus the plugin) writing identical observations simultaneously; a UNIQUE index that was changed or dropped by a migration; passing a memorySessionId whose rows were pruned mid-write.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

          promptNumber || null,
          discoveryTokens,
          observation.agent_type ?? null,
          observation.agent_id ?? null,
          contentHash,
          timestampIso,
          timestampEpoch,
          generatedByModel || null,
          observation.metadata ?? null
        ) as { id: number } | null;

        if (inserted) {
          observationIds.push(inserted.id);
          continue;
        }

        const existing = lookupExistingStmt.get(memorySessionId, contentHash) as { id: number } | null;
        if (!existing) {
          throw new Error(
            `storeObservations: ON CONFLICT without existing row for content_hash=${contentHash}`
          );
        }
        observationIds.push(existing.id);
      }

      let summaryId: number | null = null;
      if (summary) {
        const rolledUp = rollupObservationFileLists(observations);
        const filesRead = summary.files_read ?? rolledUp.files_read;
        const filesEdited = summary.files_edited ?? rolledUp.files_edited;
        const summaryStmt = this.db.prepare(`
          INSERT INTO session_summaries
          (memory_session_id, project, request, investigated, learned, completed,
           next_steps, files_read, files_edited, notes, prompt_number, discovery_tokens, created_at, created_at_epoch)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        `);

View on GitHub (pinned to d8bc9755e7)