thedotmack/claude-mem · error

agent_event source_id must equal agent_event_id

Error message

agent_event source_id must equal agent_event_id

What it means

Thrown by addSource when sourceType is 'agent_event'. The resolved agentEventId is (agentEventId ?? sourceId). If the caller also passed an explicit agentEventId that differs from sourceId, the invariant (sourceId === agentEventId for agent_event sources) is violated and the call is rejected before any DB ownership check.

Source

Thrown at src/storage/postgres/observations.ts:228

    generationJobId?: string | null;
    metadata?: JsonObject;
  }): Promise<PostgresObservationSource> {
    const observation = await queryOne<{ id: string }>(
      this.client,
      'SELECT id FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',
      [input.observationId, input.projectId, input.teamId]
    );
    if (!observation) {
      throw new Error('observation_id does not exist');
    }

    const agentEventId = input.sourceType === 'agent_event'
      ? input.agentEventId ?? input.sourceId
      : null;

    if (input.sourceType === 'agent_event') {
      if (agentEventId !== input.sourceId) {
        throw new Error('agent_event source_id must equal agent_event_id');
      }
      await assertAgentEventOwnership(this.client, input.sourceId, input.projectId, input.teamId);
    } else if (input.sourceType === 'session_summary' && !input.generationJobId) {
      await assertSessionOwnership(this.client, input.sourceId, input.projectId, input.teamId);
    } else if (input.sourceType === 'observation_reindex' && !input.generationJobId) {
      await assertObservationOwnership(this.client, input.sourceId, input.projectId, input.teamId);
    }
    if (input.generationJobId) {
      await assertGenerationJobMatchesSource(this.client, {
        generationJobId: input.generationJobId,
        projectId: input.projectId,
        teamId: input.teamId,
        sourceType: input.sourceType,
        sourceId: input.sourceId,
        agentEventId
      });
    }

View on GitHub (pinned to d768ba3643)

Solutions

  1. For agent_event sources, pass agentEventId and sourceId as the same value (or omit agentEventId so it defaults to sourceId).
  2. Decide on one canonical event id field and thread only that.

Example fix

// before
await sources.addSource({ observationId, projectId, teamId, sourceType:'agent_event', sourceId: eventId, agentEventId: otherEventId });
// after
await sources.addSource({ observationId, projectId, teamId, sourceType:'agent_event', sourceId: eventId, agentEventId: eventId });
Defensive patterns

Strategy: validation

Validate before calling

if (sourceType==='agent_event') { const id = agentEventId ?? sourceId; if (agentEventId != null && agentEventId !== sourceId) throw new TypeError('agent_event sourceId must equal agentEventId'); }

Type guard

function isAgentEventSourceConsistent(input){ if (input.sourceType!=='agent_event') return true; return input.agentEventId == null || input.agentEventId === input.sourceId; }

Prevention

When it happens

Trigger: Calling addSource() with sourceType:'agent_event' where agentEventId is set and agentEventId !== sourceId. The check runs before assertAgentEventOwnership.

Common situations: Caller setting agentEventId to a different event than sourceId; refactor that started populating agentEventId explicitly without mirroring it into sourceId; confusion about which id is canonical.

Related errors


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