{"record":{"id":"77cce5ed1f5d5fa0","repo":"thedotmack/claude-mem","slug":"session-linkage-lookup-failed-storing-event-unlin","errorCode":null,"errorMessage":"session linkage lookup failed; storing event unlinked","messagePattern":"session linkage lookup failed; storing event unlinked","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/server/routes/v1/ServerV1PostgresRoutes.ts","lineNumber":1216,"sourceCode":"\n      const platformScope = this.sessionLookupPlatformScope(rawBodies[index]);\n      const hasPlatformScope = Object.prototype.hasOwnProperty.call(platformScope, 'platformSource');\n      const cacheKey = JSON.stringify([\n        input.projectId,\n        teamId,\n        input.contentSessionId,\n        hasPlatformScope,\n        hasPlatformScope ? platformScope.platformSource ?? null : null,\n      ]);\n      let lookup = lookups.get(cacheKey);\n      if (!lookup) {\n        lookup = repo.findIdByContentSessionId({\n          contentSessionId: input.contentSessionId,\n          projectId: input.projectId,\n          teamId,\n          ...platformScope,\n        }).catch((err: unknown) => {\n          logger.warn('HTTP', 'session linkage lookup failed; storing event unlinked', {\n            error: err instanceof Error ? err.message : String(err),\n          });\n          return null;\n        });\n        lookups.set(cacheKey, lookup);\n      }\n\n      const linkedId = await lookup;\n      if (linkedId) input.serverSessionId = linkedId;\n    }));\n  }\n\n  private sessionLookupPlatformScope(body: unknown): { platformSource?: string | null } {\n    if (!body || typeof body !== 'object') return {};\n    if (!Object.prototype.hasOwnProperty.call(body, 'platformSource')) return {};\n\n    const value = (body as { platformSource?: unknown }).platformSource;\n    return {","sourceCodeStart":1198,"sourceCodeEnd":1234,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/server/routes/v1/ServerV1PostgresRoutes.ts#L1198-L1234","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the error field in the warn line: it carries the underlying Postgres error (connection, timeout, or SQL failure)","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","If statement timeouts repeat, raise statement_timeout or add the index the lookup needs on (content_session_id, project_id, team_id)","After migrations, verify the sessions table shape still matches findIdByContentSessionId"],"exampleFix":"// before\nconst linkedId = await repo.findIdByContentSessionId({ contentSessionId, projectId, teamId });\ninput.serverSessionId = linkedId ?? undefined;\n\n// after: degrade gracefully and leave a repairable linkage gap\nlet linkedId: string | null = null;\ntry {\n  linkedId = await repo.findIdByContentSessionId({ contentSessionId, projectId, teamId });\n} catch (error) {\n  logger.warn('HTTP', 'session linkage lookup failed; storing event unlinked', {\n    error: error instanceof Error ? error.message : String(error),\n  });\n}\ninput.serverSessionId = linkedId ?? undefined; // a later backfill links it","handlingStrategy":"fallback","validationCode":"// Before a bulk ingestion run, verify the sessions lookup path is healthy:\nawait pool.query(\n  'SELECT 1 FROM sessions WHERE content_session_id = $1 LIMIT 1',\n  ['probe'],\n);\n// If this throws, pause ingestion: events would be stored unlinked.","typeGuard":"function isPgDatabaseError(error: unknown): error is { code: string; detail?: string; message: string } {\n  return typeof error === 'object' && error !== null && 'code' in error\n    && typeof (error as { code: unknown }).code === 'string';\n}","tryCatchPattern":"let lookup: Promise<string | null> = repo.findIdByContentSessionId({ ... });\nlookup = lookup.catch((error: unknown) => {\n  logger.warn('HTTP', 'session linkage lookup failed; storing event unlinked', {\n    error: error instanceof Error ? error.message : String(error),\n  });\n  return null; // degrade to unlinked storage; a backfill re-links later\n});","preventionTips":["Size the Postgres pool for ingestion bursts so the lookup does not reject under load","Set statement_timeout above the p95 of the session-linkage query","Deploy schema migrations before the API version that depends on them","Schedule the session-link backfill so unlinked events are repaired, not just tolerated"],"tags":["postgres","session-linkage","event-ingestion","degraded-mode"],"backgroundTag":"database-query-failed","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}