thedotmack/claude-mem · error
generation_job_id must belong to project_id and team_id
Error message
generation_job_id must belong to project_id and team_id
What it means
Thrown by PostgresObservationGenerationJobEventsRepository.append. The insert uses INSERT ... SELECT ... FROM observation_generation_jobs WHERE id=$2 AND project_id=$3 AND team_id=$8. If zero rows match, no event row is inserted and the guard throws, meaning the referenced generation job is not owned by the given project and team.
Source
Thrown at src/storage/postgres/generation-jobs.ts:333
FROM observation_generation_jobs jobs
WHERE jobs.id = $2
AND jobs.project_id = $3
AND jobs.team_id = $8
RETURNING observation_generation_job_events.*
`,
[
input.id ?? newId(),
input.generationJobId,
input.projectId,
input.eventType,
input.statusAfter,
input.attempt ?? 0,
JSON.stringify(input.details ?? {}),
input.teamId
]
);
if (!row) {
throw new Error('generation_job_id must belong to project_id and team_id');
}
return mapJobEventRow(row!);
}
async listByJobForScope(input: {
generationJobId: string;
projectId: string;
teamId: string;
}): Promise<PostgresObservationGenerationJobEvent[]> {
const result = await this.client.query<JobEventRow>(
`
SELECT events.*
FROM observation_generation_job_events events
INNER JOIN observation_generation_jobs jobs ON jobs.id = events.generation_job_id
WHERE events.generation_job_id = $1 AND jobs.project_id = $2 AND jobs.team_id = $3
ORDER BY events.created_at ASC
`,
[input.generationJobId, input.projectId, input.teamId]View on GitHub (pinned to d768ba3643)
Solutions
- Fetch the job via getByIdForScope first; only append events if it returns non-null.
- Re-derive projectId/teamId from the job before appending events rather than trusting caller-supplied scope.
- Check whether the job was recreated (idempotency_key collision created a new row) and update the stored generationJobId.
Example fix
// before
await events.append({ generationJobId, projectId, teamId, eventType:'processing', statusAfter:'processing' });
// after
const job = await jobs.getByIdForScope({ id: generationJobId, projectId, teamId });
if (!job) { logger.warn('jobs','job not in scope, skipping event'); return; }
await events.append({ generationJobId, projectId, teamId, eventType:'processing', statusAfter:'processing' }); Defensive patterns
Strategy: validation
Validate before calling
const job = await jobs.getByIdForScope({ id: generationJobId, projectId, teamId });
if (!job) return; // or throw a domain-specific error Try / catch
try { await events.append({...}); } catch (e) { if (e.message === 'generation_job_id must belong to project_id and team_id') { /* log + drop stale job */ } else throw e; } Prevention
- Always scope job lookups before emitting events.
- Discard queued events for jobs that no longer exist rather than failing the worker.
When it happens
Trigger: Calling events.append() with a generationJobId that does not exist under the supplied projectId/teamId. The job id may be valid in another tenant, deleted, or never created.
Common situations: Worker/queue handler holding a stale job id after the job row was re-created under a new idempotency key; replaying events against the wrong tenant; passing a bullmq job id instead of the DB job id.
Related errors
- server_session_id must match the agent_event server_session_
- session_summary source_id must equal server_session_id
- observation_reindex source_id must belong to project_id and
- cannot transition observation generation job from terminal s
- illegal observation generation job transition from ${current
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/63f9ebe2c8e01e71.
Report an issue: GitHub.