thedotmack/claude-mem · error · Error

observation_id does not exist

Error message

observation_id does not exist

What it means

addSource first verifies the target observation exists within the caller's project_id and team_id scope before attaching a source row. If the SELECT finds no row, the given observationId is unknown, belongs to a different project/team, or was already deleted, so linking is refused. This prevents sources being attached to observations outside the caller's tenant scope.

Solutions

  1. Verify the observation row exists: SELECT id FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3 with the exact values passed to addSource.
  2. Create the observation first (observations repository create) before calling addSource.
  3. Confirm projectId and teamId are not swapped or sourced from the wrong tenant context.
  4. Re-run the generation job so the observation is re-created if it was deleted mid-pipeline.

Example fix

// before
await sources.addSource({ observationId: staleId, projectId, teamId, ... });
// after
const obs = await observations.getById({ id: staleId, projectId, teamId });
if (!obs) throw new Error(`observation ${staleId} missing for project ${projectId}`);
await sources.addSource({ observationId: obs.id, projectId, teamId, ... });
Defensive patterns

Strategy: validation

Validate before calling

const exists = await db.oneOrNone(
  'SELECT 1 FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',
  [observationId, projectId, teamId]
);
if (!exists) throw new Error(`observation ${observationId} not found in scope before addSource`);

Type guard

function isKnownObservation<T extends { id: string }>(row: T | null | undefined): row is T {
  return row != null && typeof row.id === 'string' && row.id.length > 0;
}

Try / catch

try {
  await repo.addSource(input);
} catch (e) {
  if (e instanceof Error && e.message === 'observation_id does not exist') {
    logger.warn('skip addSource: observation missing', { id: input.observationId });
    return null; // or re-create observation and retry once
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling PostgresObservationSourcesRepository.addSource with an observationId that (a) was never created, (b) exists under a different projectId/teamId than the one passed, or (c) was deleted before persistGeneratedObservations ran.

Common situations: Persisting LLM-generated observations after the parent observation row was rolled back or deleted; cross-tenant ID reuse after copying data between environments; stale observationId cached from a previous run; projectId/teamId swapped in the call.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/4da2853bafb5ad54. Report an issue: GitHub.

Appendix: source

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

  async addSource(input: {
    id?: string;
    observationId: string;
    projectId: string;
    teamId: string;
    sourceType: ObservationSourceType;
    sourceId: string;
    agentEventId?: string | null;
    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, {

View on GitHub (pinned to d8bc9755e7)