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
- Look up the job and confirm its source_type/source_id columns equal the sourceType/sourceId you pass to addSource.
- Pass the generation job id that was actually created for this specific sourceId/sourceType pair.
- 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
- Keep a Map from `${sourceType}:${sourceId}` to its generation job id instead of reusing one id.
- Pair jobs to sources by key, never by loop index.
- Add an assertion in the generation pipeline that each job is consumed exactly once for its own source.
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
- generation_job_id agent_event_id must match observation…
- agent_event_id must belong to project_id and team_id
- agent_event source_id must belong to project_id and team_id
- agent_event source_id must equal agent_event_id
- audit log insert failed
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)