{"record":{"id":"63f9ebe2c8e01e71","repo":"thedotmack/claude-mem","slug":"generation-job-id-must-belong-to-project-id-and-te","errorCode":null,"errorMessage":"generation_job_id must belong to project_id and team_id","messagePattern":"generation_job_id must belong to project_id and team_id","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":342,"sourceCode":"        FROM observation_generation_jobs jobs\n        WHERE jobs.id = $2\n          AND jobs.project_id = $3\n          AND jobs.team_id = $8\n        RETURNING observation_generation_job_events.*\n      `,\n      [\n        input.id ?? newId(),\n        input.generationJobId,\n        input.projectId,\n        input.eventType,\n        input.statusAfter,\n        input.attempt ?? 0,\n        JSON.stringify(input.details ?? {}),\n        input.teamId\n      ]\n    );\n    if (!row) {\n      throw new Error('generation_job_id must belong to project_id and team_id');\n    }\n    return mapJobEventRow(row!);\n  }\n\n  async listByJobForScope(input: {\n    generationJobId: string;\n    projectId: string;\n    teamId: string;\n  }): Promise<PostgresObservationGenerationJobEvent[]> {\n    const result = await this.client.query<JobEventRow>(\n      `\n        SELECT events.*\n        FROM observation_generation_job_events events\n        INNER JOIN observation_generation_jobs jobs ON jobs.id = events.generation_job_id\n        WHERE events.generation_job_id = $1 AND jobs.project_id = $2 AND jobs.team_id = $3\n        ORDER BY events.created_at ASC\n      `,\n      [input.generationJobId, input.projectId, input.teamId]","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/generation-jobs.ts#L324-L360","documentation":"append() on the job-events repository inserts a job event but only succeeds if the generation_job_id belongs to the given project_id and team_id; if the INSERT ... RETURNING matches no row, it throws. This prevents appending events to jobs outside the caller's scope.","triggerScenarios":"Calling append() (or markGenerationFailed / persistGeneratedObservations / retryGenerationJob / cancelGenerationJob / txResult / txResults) with a generationJobId that does not exist, or exists under a different project_id/team_id than the input scope.","commonSituations":"Worker processing a job whose row was concurrently deleted or cancelled; cross-tenant id reuse after data deletion; passing the observation id instead of the job id; stale job id from a previous database reset.","solutions":["Confirm the generation job exists under the same project_id/team_id before appending events","Refetch the job by id scoped to your project/team and use that row's id","Check for concurrent cancellation/deletion of the job and handle the not-found case gracefully","Verify you are not passing a different entity's id (observation/event) as generationJobId"],"exampleFix":"// before\nawait jobEvents.append({ generationJobId: someId, projectId, teamId, type: 'note' });\n// after\nconst job = await jobs.getByIdForScope(someId, projectId, teamId);\nif (!job) throw new Error('job not in scope');\nawait jobEvents.append({ generationJobId: job.id, projectId, teamId, type: 'note' });","handlingStrategy":"validation","validationCode":"const job = await client.query('SELECT 1 FROM observation_generation_jobs WHERE id=$1 AND project_id=$2 AND team_id=$3', [jobId, projectId, teamId]);\nif (job.rowCount === 0) throw new Error('generation job not in scope');","typeGuard":null,"tryCatchPattern":"try {\n  await jobEvents.append(input);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('generation_job_id must belong')) {\n    logger.warn({ jobId: input.generationJobId }, 'job missing or out of scope; skipping event append');\n    return null;\n  }\n  throw err;\n}","preventionTips":["Refetch the job scoped to your project/team before appending events","Handle concurrent job deletion/cancellation by treating append-not-found as benign","Distinguish job ids from observation/event ids clearly in your data model"],"tags":["postgres","multi-tenancy","foreign-key"],"backgroundTag":"resource-not-found","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"}