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

  1. Drop the serverSessionId from the create() call and let normalizeSourceModel/validateSource derive it, when you only need the event's own session.
  2. 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.
  3. 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

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


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/ff8967aecbe5d902. Report an issue: GitHub.