{"record":{"id":"4da2853bafb5ad54","repo":"thedotmack/claude-mem","slug":"observation-id-does-not-exist","errorCode":null,"errorMessage":"observation_id does not exist","messagePattern":"observation_id does not exist","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/observations.ts","lineNumber":219,"sourceCode":"\n  async addSource(input: {\n    id?: string;\n    observationId: string;\n    projectId: string;\n    teamId: string;\n    sourceType: ObservationSourceType;\n    sourceId: string;\n    agentEventId?: string | null;\n    generationJobId?: string | null;\n    metadata?: JsonObject;\n  }): Promise<PostgresObservationSource> {\n    const observation = await queryOne<{ id: string }>(\n      this.client,\n      'SELECT id FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',\n      [input.observationId, input.projectId, input.teamId]\n    );\n    if (!observation) {\n      throw new Error('observation_id does not exist');\n    }\n\n    const agentEventId = input.sourceType === 'agent_event'\n      ? input.agentEventId ?? input.sourceId\n      : null;\n\n    if (input.sourceType === 'agent_event') {\n      if (agentEventId !== input.sourceId) {\n        throw new Error('agent_event source_id must equal agent_event_id');\n      }\n      await assertAgentEventOwnership(this.client, input.sourceId, input.projectId, input.teamId);\n    } else if (input.sourceType === 'session_summary' && !input.generationJobId) {\n      await assertSessionOwnership(this.client, input.sourceId, input.projectId, input.teamId);\n    } else if (input.sourceType === 'observation_reindex' && !input.generationJobId) {\n      await assertObservationOwnership(this.client, input.sourceId, input.projectId, input.teamId);\n    }\n    if (input.generationJobId) {\n      await assertGenerationJobMatchesSource(this.client, {","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/observations.ts#L201-L237","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait sources.addSource({ observationId: staleId, projectId, teamId, ... });\n// after\nconst obs = await observations.getById({ id: staleId, projectId, teamId });\nif (!obs) throw new Error(`observation ${staleId} missing for project ${projectId}`);\nawait sources.addSource({ observationId: obs.id, projectId, teamId, ... });","handlingStrategy":"validation","validationCode":"const exists = await db.oneOrNone(\n  'SELECT 1 FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',\n  [observationId, projectId, teamId]\n);\nif (!exists) throw new Error(`observation ${observationId} not found in scope before addSource`);","typeGuard":"function isKnownObservation<T extends { id: string }>(row: T | null | undefined): row is T {\n  return row != null && typeof row.id === 'string' && row.id.length > 0;\n}","tryCatchPattern":"try {\n  await repo.addSource(input);\n} catch (e) {\n  if (e instanceof Error && e.message === 'observation_id does not exist') {\n    logger.warn('skip addSource: observation missing', { id: input.observationId });\n    return null; // or re-create observation and retry once\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["postgres","foreign-key","tenant-scope"],"backgroundTag":"record-not-found","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}