{"record":{"id":"62705560e62eaa79","repo":"thedotmack/claude-mem","slug":"generation-job-id-source-model-must-match-observat","errorCode":null,"errorMessage":"generation_job_id source model must match observation source","messagePattern":"generation_job_id source model must match observation source","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/storage/postgres/observations.ts","lineNumber":353,"sourceCode":"  const row = await queryOne<{\n    id: string;\n    source_type: string;\n    source_id: string;\n    agent_event_id: string | null;\n  }>(\n    client,\n    `\n      SELECT id, source_type, source_id, agent_event_id\n      FROM observation_generation_jobs\n      WHERE id = $1 AND project_id = $2 AND team_id = $3\n    `,\n    [input.generationJobId, input.projectId, input.teamId]\n  );\n  if (!row) {\n    throw new Error('generation_job_id must belong to project_id and team_id');\n  }\n  if (row.source_type !== input.sourceType || row.source_id !== input.sourceId) {\n    throw new Error('generation_job_id source model must match observation source');\n  }\n  if (input.sourceType === 'agent_event' && row.agent_event_id !== input.agentEventId) {\n    throw new Error('generation_job_id agent_event_id must match observation source');\n  }\n}\n\nasync function assertAgentEventOwnership(\n  client: PostgresQueryable,\n  agentEventId: string,\n  projectId: string,\n  teamId: string\n): Promise<void> {\n  const row = await queryOne<{ id: string }>(\n    client,\n    'SELECT id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',\n    [agentEventId, projectId, teamId]\n  );\n  if (!row) {","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/storage/postgres/observations.ts#L335-L371","documentation":"Thrown by assertGenerationJobMatchesSource after confirming the job exists in scope. It compares the job's stored source_type and source_id against the sourceType/sourceId supplied to addSource. A mismatch means the observation's source does not match the source the job was created from, so they cannot be linked.","triggerScenarios":"addSource with a generationJobId where the job's (source_type, source_id) differ from the (sourceType, sourceId) passed in. E.g. job created from agent_event X but you try to link a session_summary source.","commonSituations":"Mixing up jobs for different sources; reusing a job id across reprocessed sources; UI passing a default job id regardless of which source is being linked.","solutions":["Look up the job and use its source_type/source_id as the source for the observation.","Create a dedicated job for the exact (sourceType, sourceId) before linking.","Do not share one job id across different sources."],"exampleFix":"// before\nawait sources.addSource({ observationId, projectId, teamId, sourceType:'session_summary', sourceId: sessionId, generationJobId: agentEventJobId });\n// after\nconst job = await jobs.getByIdForScope({ id: generationJobId, projectId, teamId });\nawait sources.addSource({ observationId, projectId, teamId, sourceType: job.sourceType, sourceId: job.sourceId, generationJobId: job.id });","handlingStrategy":"validation","validationCode":"const job = await jobs.getByIdForScope({ id: generationJobId, projectId, teamId });\nif (job.sourceType !== sourceType || job.sourceId !== sourceId) throw new Error('source/job mismatch');","typeGuard":"function jobMatchesSource(job, input){ return job.sourceType===input.sourceType && job.sourceId===input.sourceId; }","tryCatchPattern":null,"preventionTips":["One job per (sourceType, sourceId); never reuse across sources."],"tags":["postgres","validation","observations","invariant"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}