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
- 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.
- Create the observation first (observations repository create) before calling addSource.
- Confirm projectId and teamId are not swapped or sourced from the wrong tenant context.
- 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
- Always create the observation and add sources in the same transaction so rollback removes both.
- Pass projectId/teamId from a single tenant-context object, never from mixed sources.
- Log observationId immediately after creation and reuse that value, not a cached one.
- Add an integration test that attaches a source to a freshly created observation.
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
- agent_event_id must belong to project_id and team_id
- generation_job_id must belong to project_id and team_id
- generation_job_id must belong to project_id and team_id
- observation_reindex source_id must belong to project_id and…
- agent_event source_id must belong to project_id and team_id
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)