thedotmack/claude-mem · error · Error

generation_job_id source model must match observation source

Error message

generation_job_id source model must match observation source

What it means

After locating the generation job, its stored source_type and source_id are compared with the observation source being linked. Any mismatch means the job did not run for this particular source, so attaching it to the observation would misattribute provenance; the library throws instead.

Solutions

  1. Look up the job and confirm its source_type/source_id columns equal the sourceType/sourceId you pass to addSource.
  2. Pass the generation job id that was actually created for this specific sourceId/sourceType pair.
  3. Fix loop code that assigns a single generationJobId across multiple different sources.

Example fix

// before
for (const src of sources) {
  await repo.addSource({ ...src, generationJobId: firstJobId });
}
// after
for (const src of sources) {
  const job = jobsBySource.get(`${src.sourceType}:${src.sourceId}`);
  await repo.addSource({ ...src, generationJobId: job?.id ?? null });
}
Defensive patterns

Strategy: validation

Validate before calling

const job = await db.one(
  'SELECT source_type, source_id FROM observation_generation_jobs WHERE id = $1', [generationJobId]
);
if (job.source_type !== sourceType || job.source_id !== sourceId) {
  throw new Error(`job ${generationJobId} does not match ${sourceType}:${sourceId}`);
}

Type guard

function jobMatchesSource(job: { source_type: string; source_id: string }, src: { sourceType: string; sourceId: string }): boolean {
  return job.source_type === src.sourceType && job.source_id === src.sourceId;
}

Try / catch

try {
  await repo.addSource(input);
} catch (e) {
  if (e instanceof Error && e.message === 'generation_job_id source model must match observation source') {
    logger.warn('job/source pairing mismatch', { jobId: input.generationJobId, src: input.sourceId });
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: addSource called with a valid but unrelated generationJobId — the job's (source_type, source_id) differ from the input's sourceType/sourceId, e.g. attaching job X (for event A) to a source record for event B.

Common situations: Looping over sources and reusing one job id for all of them; off-by-one pairing of jobs to sources after reordering; copying job ids between rows; retries that lost the original job-to-source mapping.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/62705560e62eaa79. Report an issue: GitHub.

Appendix: source

Thrown at src/storage/postgres/observations.ts:353

  const row = await queryOne<{
    id: string;
    source_type: string;
    source_id: string;
    agent_event_id: string | null;
  }>(
    client,
    `
      SELECT id, source_type, source_id, agent_event_id
      FROM observation_generation_jobs
      WHERE id = $1 AND project_id = $2 AND team_id = $3
    `,
    [input.generationJobId, input.projectId, input.teamId]
  );
  if (!row) {
    throw new Error('generation_job_id must belong to project_id and team_id');
  }
  if (row.source_type !== input.sourceType || row.source_id !== input.sourceId) {
    throw new Error('generation_job_id source model must match observation source');
  }
  if (input.sourceType === 'agent_event' && row.agent_event_id !== input.agentEventId) {
    throw new Error('generation_job_id agent_event_id must match observation source');
  }
}

async function assertAgentEventOwnership(
  client: PostgresQueryable,
  agentEventId: string,
  projectId: string,
  teamId: string
): Promise<void> {
  const row = await queryOne<{ id: string }>(
    client,
    'SELECT id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',
    [agentEventId, projectId, teamId]
  );
  if (!row) {

View on GitHub (pinned to d8bc9755e7)