{"record":{"id":"4528765b56feaf0b","repo":"thedotmack/claude-mem","slug":"observation-reindex-source-id-must-belong-to-proje","errorCode":null,"errorMessage":"observation_reindex source_id must belong to project_id and team_id","messagePattern":"observation_reindex source_id must belong to project_id and team_id","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":296,"sourceCode":"      return;\n    }\n\n    if (input.sourceType === 'session_summary') {\n      const sessionId = input.serverSessionId ?? input.sourceId;\n      await assertSessionOwnership(this.client, sessionId, input.projectId, input.teamId);\n      if (input.sourceId !== sessionId) {\n        throw new Error('session_summary source_id must equal server_session_id');\n      }\n      return;\n    }\n\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.sourceId, input.projectId, input.teamId]\n    );\n    if (!observation) {\n      throw new Error('observation_reindex source_id must belong to project_id and team_id');\n    }\n    if (input.serverSessionId) {\n      await assertSessionOwnership(this.client, input.serverSessionId, input.projectId, input.teamId);\n    }\n  }\n}\n\nexport class PostgresObservationGenerationJobEventsRepository {\n  constructor(private client: PostgresQueryable) {}\n\n  async append(input: {\n    id?: string;\n    generationJobId: string;\n    projectId: string;\n    teamId: string;\n    eventType: ObservationGenerationJobEventType;\n    statusAfter: ObservationGenerationJobStatus;\n    attempt?: number;","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/generation-jobs.ts#L278-L314","documentation":"For sourceType 'observation_reindex', validateSource requires an observation row to exist with the given id under the job's project_id and team_id; otherwise the create is rejected. This ensures reindex jobs only target observations in the caller's scope.","triggerScenarios":"create() with sourceType 'observation_reindex' where no observation matches the sourceId/projectId/teamId combination (id does not exist, or belongs to another project/team).","commonSituations":"Reindexing after a data deletion removed the observation; referencing an observation id from another tenant; using a memory/summary id instead of an observation id; id passed with different casing/format.","solutions":["Verify the observation exists with a scoped SELECT (id, project_id, team_id) before creating the job","Use the correct observation id (not a memory or event id) from the same project/team","If the observation was deleted, restore it or skip the reindex job","Check tenant context: projectId/teamId must be the ones the observation was created under"],"exampleFix":"// before\nawait jobs.create({ sourceType: 'observation_reindex', sourceId: obsId, projectId, teamId });\n// after\nconst obs = await client.query('SELECT 1 FROM observations WHERE id=$1 AND project_id=$2 AND team_id=$3', [obsId, projectId, teamId]);\nif (obs.rowCount === 0) throw new Error('observation not in scope');\nawait jobs.create({ sourceType: 'observation_reindex', sourceId: obsId, projectId, teamId });","handlingStrategy":"validation","validationCode":"const obs = await client.query('SELECT 1 FROM observations WHERE id=$1 AND project_id=$2 AND team_id=$3', [sourceId, projectId, teamId]);\nif (obs.rowCount === 0) throw new Error('observation not found in scope');","typeGuard":null,"tryCatchPattern":"try {\n  await jobs.create(input);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('observation_reindex source_id must belong')) {\n    throw new ScopeError('Observation missing or out of tenant scope');\n  }\n  throw err;\n}","preventionTips":["Verify observation existence with scoped queries before creating reindex jobs","Skip reindex jobs whose source was deleted; log instead of throwing","Keep observation ids in the same tenant context as the job"],"tags":["postgres","multi-tenancy","validation"],"backgroundTag":"resource-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"}