thedotmack/claude-mem · warning

session linkage lookup failed; storing event unlinked

Error message

session linkage lookup failed; storing event unlinked

What it means

In the server's event ingestion route, each event is linked to a server session via repo.findIdByContentSessionId (results memoized per request in a promise cache). If the Postgres lookup rejects, the route logs this warning, resolves the lookup to null, and stores the event unlinked (no serverSessionId) rather than failing the request. Linkage must be repaired later by a backfill.

Source

Thrown at src/server/routes/v1/ServerV1PostgresRoutes.ts:1216

      const platformScope = this.sessionLookupPlatformScope(rawBodies[index]);
      const hasPlatformScope = Object.prototype.hasOwnProperty.call(platformScope, 'platformSource');
      const cacheKey = JSON.stringify([
        input.projectId,
        teamId,
        input.contentSessionId,
        hasPlatformScope,
        hasPlatformScope ? platformScope.platformSource ?? null : null,
      ]);
      let lookup = lookups.get(cacheKey);
      if (!lookup) {
        lookup = repo.findIdByContentSessionId({
          contentSessionId: input.contentSessionId,
          projectId: input.projectId,
          teamId,
          ...platformScope,
        }).catch((err: unknown) => {
          logger.warn('HTTP', 'session linkage lookup failed; storing event unlinked', {
            error: err instanceof Error ? err.message : String(err),
          });
          return null;
        });
        lookups.set(cacheKey, lookup);
      }

      const linkedId = await lookup;
      if (linkedId) input.serverSessionId = linkedId;
    }));
  }

  private sessionLookupPlatformScope(body: unknown): { platformSource?: string | null } {
    if (!body || typeof body !== 'object') return {};
    if (!Object.prototype.hasOwnProperty.call(body, 'platformSource')) return {};

    const value = (body as { platformSource?: unknown }).platformSource;
    return {

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Read the error field in the warn line: it carries the underlying Postgres error (connection, timeout, or SQL failure)
  2. For transient causes (restart, failover) take no immediate action: events are stored unlinked, so run the session-link backfill or repair job to re-link them
  3. If statement timeouts repeat, raise statement_timeout or add the index the lookup needs on (content_session_id, project_id, team_id)
  4. After migrations, verify the sessions table shape still matches findIdByContentSessionId

Example fix

// before
const linkedId = await repo.findIdByContentSessionId({ contentSessionId, projectId, teamId });
input.serverSessionId = linkedId ?? undefined;

// after: degrade gracefully and leave a repairable linkage gap
let linkedId: string | null = null;
try {
  linkedId = await repo.findIdByContentSessionId({ contentSessionId, projectId, teamId });
} catch (error) {
  logger.warn('HTTP', 'session linkage lookup failed; storing event unlinked', {
    error: error instanceof Error ? error.message : String(error),
  });
}
input.serverSessionId = linkedId ?? undefined; // a later backfill links it
Defensive patterns

Strategy: fallback

Validate before calling

// Before a bulk ingestion run, verify the sessions lookup path is healthy:
await pool.query(
  'SELECT 1 FROM sessions WHERE content_session_id = $1 LIMIT 1',
  ['probe'],
);
// If this throws, pause ingestion: events would be stored unlinked.

Type guard

function isPgDatabaseError(error: unknown): error is { code: string; detail?: string; message: string } {
  return typeof error === 'object' && error !== null && 'code' in error
    && typeof (error as { code: unknown }).code === 'string';
}

Try / catch

let lookup: Promise<string | null> = repo.findIdByContentSessionId({ ... });
lookup = lookup.catch((error: unknown) => {
  logger.warn('HTTP', 'session linkage lookup failed; storing event unlinked', {
    error: error instanceof Error ? error.message : String(error),
  });
  return null; // degrade to unlinked storage; a backfill re-links later
});

Prevention

When it happens

Trigger: POSTing events to the v1 ingestion endpoint while Postgres has a transient failure (connection drop, pool exhaustion, statement timeout, failover) or after a schema change to the sessions table that breaks the lookup query.

Common situations: PgBouncer or RDS idle timeouts reaping connections mid-request; traffic bursts exhausting the pool; a migration deployed while traffic flows; database restart during a deploy window.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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