{"record":{"id":"7b9f2507124fff5a","repo":"thedotmack/claude-mem","slug":"generation-job-id-agent-event-id-must-match-observ","errorCode":null,"errorMessage":"generation_job_id agent_event_id must match observation source","messagePattern":"generation_job_id agent_event_id must match observation source","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/observations.ts","lineNumber":356,"sourceCode":"    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) {\n    throw new Error('agent_event_id must belong to project_id and team_id');\n  }\n}","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/observations.ts#L338-L374","documentation":"For sourceType 'agent_event', the generation job row's agent_event_id must match the resolved agentEventId of the observation source being linked. A mismatch means the job was run against a different agent event than the one this source points at, so the link is rejected to keep provenance exact.","triggerScenarios":"addSource with sourceType='agent_event', a generationJobId whose row stores a different agent_event_id than the source's agent event id — e.g. two agent events handled by swapped job variables.","commonSituations":"Parallel generation of observations for many agent events where job results are paired to the wrong event; reprocessing events in a different order; null vs non-null agent_event_id between job and source.","solutions":["Compare the job row's agent_event_id with the event id you pass; use the job created for that exact event.","Re-run generation for the correct agent event and link the new job id.","Fix any parallel/retry logic that pairs jobs to events by index instead of by id."],"exampleFix":"// before\nawait repo.addSource({ sourceType: 'agent_event', sourceId: evt.id, generationJobId: jobForOtherEvent.id, ... });\n// after\nconst job = await db.one(\n  'SELECT id FROM observation_generation_jobs WHERE agent_event_id = $1', [evt.id]\n);\nawait repo.addSource({ sourceType: 'agent_event', sourceId: evt.id, generationJobId: job.id, ... });","handlingStrategy":"validation","validationCode":"const job = await db.one(\n  'SELECT agent_event_id FROM observation_generation_jobs WHERE id = $1', [generationJobId]\n);\nif (job.agent_event_id !== agentEventId) {\n  throw new Error(`job ${generationJobId} belongs to event ${job.agent_event_id}, not ${agentEventId}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await repo.addSource(input);\n} catch (e) {\n  if (e instanceof Error && e.message === 'generation_job_id agent_event_id must match observation source') {\n    logger.warn('agent_event/job mismatch', { jobId: input.generationJobId });\n    return null;\n  }\n  throw e;\n}","preventionTips":["In concurrent generation loops, key results by agent event id, not completion order.","Store the agentEventId on the job result object and pass it through unchanged.","Add a regression test that generates observations for two events in parallel and links them."],"tags":["postgres","provenance","agent-events"],"backgroundTag":"invalid-argument-value","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"}