thedotmack/claude-mem · error
server_session_id must match the agent_event server_session_
Error message
server_session_id must match the agent_event server_session_id
What it means
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.
Source
Thrown at src/storage/postgres/generation-jobs.ts:266
sourceId: string;
agentEventId?: string | null;
serverSessionId?: string | null;
}): Promise<void> {
await assertProjectOwnership(this.client, input.projectId, input.teamId);
if (input.sourceType === 'agent_event') {
const eventId = input.agentEventId ?? input.sourceId;
const row = await queryOne<{ id: string; server_session_id: string | null }>(
this.client,
'SELECT id, server_session_id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',
[eventId, input.projectId, input.teamId]
);
if (!row || input.sourceId !== eventId) {
throw new Error('agent_event source_id must belong to project_id and team_id');
}
if (input.serverSessionId) {
await assertSessionOwnership(this.client, input.serverSessionId, input.projectId, input.teamId);
if (row.server_session_id && row.server_session_id !== input.serverSessionId) {
throw new Error('server_session_id must match the agent_event server_session_id');
}
}
return;
}
if (input.sourceType === 'session_summary') {
const sessionId = input.serverSessionId ?? input.sourceId;
await assertSessionOwnership(this.client, sessionId, input.projectId, input.teamId);
if (input.sourceId !== sessionId) {
throw new Error('session_summary source_id must equal server_session_id');
}
return;
}
const observation = await queryOne<{ id: string }>(
this.client,
'SELECT id FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',
[input.sourceId, input.projectId, input.teamId]View on GitHub (pinned to d768ba3643)
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.
Example fix
// before
await jobs.create({ sourceType:'agent_event', sourceId: agentEventId, serverSessionId: someOtherSessionId, ... });
// after
const evt = await agentEvents.getByIdForScope({ id: agentEventId, projectId, teamId });
await jobs.create({ sourceType:'agent_event', sourceId: agentEventId, serverSessionId: evt?.serverSessionId ?? undefined, ... }); Defensive patterns
Strategy: validation
Validate before calling
async function resolveEventSession(client, agentEventId, projectId, teamId) {
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]);
return r.rows[0]?.server_session_id ?? null;
}
// before create(): if you pass serverSessionId, it must equal resolveEventSession(...) or be null/undefined. Type guard
async function eventSessionMatches(client, agentEventId, serverSessionId, projectId, teamId) {
const stored = await resolveEventSession(client, agentEventId, projectId, teamId);
return stored == null || stored === serverSessionId;
} Try / catch
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; } Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- observation_reindex source_id must belong to project_id and
- session_summary source_id must equal server_session_id
- generation_job_id must belong to project_id and team_id
- observation_id does not exist
- generation_job_id must belong to project_id and team_id
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/ff8967aecbe5d902.
Report an issue: GitHub.