{"record":{"id":"ff8967aecbe5d902","repo":"thedotmack/claude-mem","slug":"server-session-id-must-match-the-agent-event-serve","errorCode":null,"errorMessage":"server_session_id must match the agent_event server_session_id","messagePattern":"server_session_id must match the agent_event server_session_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":266,"sourceCode":"    sourceId: string;\n    agentEventId?: string | null;\n    serverSessionId?: string | null;\n  }): Promise<void> {\n    await assertProjectOwnership(this.client, input.projectId, input.teamId);\n    if (input.sourceType === 'agent_event') {\n      const eventId = input.agentEventId ?? input.sourceId;\n      const row = await queryOne<{ id: string; server_session_id: string | null }>(\n        this.client,\n        'SELECT id, server_session_id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',\n        [eventId, input.projectId, input.teamId]\n      );\n      if (!row || input.sourceId !== eventId) {\n        throw new Error('agent_event source_id must belong to project_id and team_id');\n      }\n      if (input.serverSessionId) {\n        await assertSessionOwnership(this.client, input.serverSessionId, input.projectId, input.teamId);\n        if (row.server_session_id && row.server_session_id !== input.serverSessionId) {\n          throw new Error('server_session_id must match the agent_event server_session_id');\n        }\n      }\n      return;\n    }\n\n    if (input.sourceType === 'session_summary') {\n      const sessionId = input.serverSessionId ?? input.sourceId;\n      await assertSessionOwnership(this.client, sessionId, input.projectId, input.teamId);\n      if (input.sourceId !== sessionId) {\n        throw new Error('session_summary source_id must equal server_session_id');\n      }\n      return;\n    }\n\n    const observation = await queryOne<{ id: string }>(\n      this.client,\n      'SELECT id FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',\n      [input.sourceId, input.projectId, input.teamId]","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/storage/postgres/generation-jobs.ts#L248-L284","documentation":"Thrown by PostgresObservationGenerationJobRepository.validateSource when creating a job whose sourceType is 'agent_event'. After resolving the agent_event row, the code checks that any caller-supplied serverSessionId agrees with the server_session_id already stored on that agent_event row. A mismatch means the caller is trying to attach the event to a different session than the one the event was recorded under.","triggerScenarios":"Calling repository.create() with sourceType:'agent_event' AND a serverSessionId that differs from the non-null server_session_id persisted on the matching agent_events row. The lookup is by (agent_event_id, project_id, team_id); the row must exist (else a different error fires first).","commonSituations":"Cross-session reuse of an agent_event id, stale cached session id after a session was rotated/recreated, or copy-paste of a serverSessionId from one event into a job create call for another event. Also seen when an orchestrator passes a default session id without checking whether the event already has one.","solutions":["Drop the serverSessionId from the create() call and let normalizeSourceModel/validateSource derive it, when you only need the event's own session.","Read the agent_event first (by id within project+team scope), use its server_session_id as the job's serverSessionId, and pass that exact value.","If you intentionally want a different session, change the sourceType away from 'agent_event' or create a new agent_event under the target session first."],"exampleFix":"// before\nawait jobs.create({ sourceType:'agent_event', sourceId: agentEventId, serverSessionId: someOtherSessionId, ... });\n// after\nconst evt = await agentEvents.getByIdForScope({ id: agentEventId, projectId, teamId });\nawait jobs.create({ sourceType:'agent_event', sourceId: agentEventId, serverSessionId: evt?.serverSessionId ?? undefined, ... });","handlingStrategy":"validation","validationCode":"async function resolveEventSession(client, agentEventId, projectId, teamId) {\n  const r = await client.query('SELECT server_session_id FROM agent_events WHERE id=$1 AND project_id=$2 AND team_id=$3', [agentEventId, projectId, teamId]);\n  return r.rows[0]?.server_session_id ?? null;\n}\n// before create(): if you pass serverSessionId, it must equal resolveEventSession(...) or be null/undefined.","typeGuard":"async function eventSessionMatches(client, agentEventId, serverSessionId, projectId, teamId) {\n  const stored = await resolveEventSession(client, agentEventId, projectId, teamId);\n  return stored == null || stored === serverSessionId;\n}","tryCatchPattern":"try { await jobs.create({...}); } catch (e) { if (e.message === 'server_session_id must match the agent_event server_session_id') { /* drop serverSessionId and retry */ } else throw e; }","preventionTips":["Do not pass serverSessionId for agent_event jobs unless you read it from the event row.","Treat the event's server_session_id as authoritative."],"tags":["postgres","validation","multi-tenant","ownership","generation-jobs"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}