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 assertJobOwnership (private helper) when PostgresObservationRepository.create is called with a createdByJobId. It looks up observation_generation_jobs by (id, project_id, team_id); no row means the job is not owned by that tenant, so the observation cannot be attributed to it.

Source

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

}): string {
  return `generation:v1:${input.generationJobId}:${input.parsedObservationIndex}:${deterministicKey([
    canonicalJson(input.content.trim())
  ])}`;
}

async function assertJobOwnership(
  client: PostgresQueryable,
  generationJobId: string,
  projectId: string,
  teamId: string
): Promise<void> {
  const row = await queryOne<{ id: string }>(
    client,
    'SELECT id FROM observation_generation_jobs WHERE id = $1 AND project_id = $2 AND team_id = $3',
    [generationJobId, projectId, teamId]
  );
  if (!row) {
    throw new Error('generation_job_id must belong to project_id and team_id');
  }
}

async function assertGenerationJobMatchesSource(
  client: PostgresQueryable,
  input: {
    generationJobId: string;
    projectId: string;
    teamId: string;
    sourceType: ObservationSourceType;
    sourceId: string;
    agentEventId: string | null;
  }
): Promise<void> {
  if (input.sourceType === 'manual') {
    throw new Error('manual observation sources cannot be linked to a generation_job_id');
  }

View on GitHub (pinned to d768ba3643)

Solutions

  1. Fetch the job with jobs.getByIdForScope before setting createdByJobId.
  2. Omit createdByJobId when the observation is not produced by a tracked job.
  3. Thread the exact job id returned by jobs.create() rather than an external id.

Example fix

// before
await observations.create({ projectId, teamId, content, createdByJobId: maybeStaleId });
// after
const job = await jobs.getByIdForScope({ id: createdByJobId, projectId, teamId });
if (!job) throw new Error('job not in scope');
await observations.create({ projectId, teamId, content, createdByJobId: job.id });
Defensive patterns

Strategy: validation

Validate before calling

if (createdByJobId) { const job = await jobs.getByIdForScope({ id: createdByJobId, projectId, teamId }); if (!job) throw new Error('job not in scope'); }

Prevention

When it happens

Trigger: Calling observations.create({createdByJobId}) where the job id is absent or belongs to a different project/team. Project/session ownership is checked first; this fires only when createdByJobId is set and unresolvable.

Common situations: Recording an observation with a stale job id after the job row changed; cross-tenant job id; passing a bullmq job id rather than the DB job id; creating observations outside the worker that owns the job.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/a584ffc9878cc5e6. Report an issue: GitHub.