{"record":{"id":"a023ce5aafeded3b","repo":"thedotmack/claude-mem","slug":"cannot-retry-observation-generation-job-after-max","errorCode":null,"errorMessage":"cannot retry observation generation job after max_attempts is reached","messagePattern":"cannot retry observation generation job after max_attempts is reached","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":424,"sourceCode":"\nfunction assertValidJobStatusTransition(\n  current: PostgresObservationGenerationJob,\n  nextStatus: ObservationGenerationJobStatus\n): void {\n  if (TERMINAL_JOB_STATUSES.has(current.status)) {\n    throw new Error(`cannot transition observation generation job from terminal status ${current.status}`);\n  }\n\n  if (!ALLOWED_JOB_TRANSITIONS[current.status].includes(nextStatus)) {\n    throw new Error(`illegal observation generation job transition from ${current.status} to ${nextStatus}`);\n  }\n\n  if (nextStatus === 'processing' && current.attempts >= current.maxAttempts) {\n    throw new Error('cannot process observation generation job after max_attempts is reached');\n  }\n\n  if (nextStatus === 'queued' && current.attempts >= current.maxAttempts) {\n    throw new Error('cannot retry observation generation job after max_attempts is reached');\n  }\n}\n\nfunction mapJobRow(row: JobRow): PostgresObservationGenerationJob {\n  return {\n    id: row.id,\n    projectId: row.project_id,\n    teamId: row.team_id,\n    agentEventId: row.agent_event_id,\n    sourceType: row.source_type,\n    sourceId: row.source_id,\n    serverSessionId: row.server_session_id,\n    jobType: row.job_type,\n    status: row.status,\n    idempotencyKey: row.idempotency_key,\n    bullmqJobId: row.bullmq_job_id,\n    attempts: row.attempts,\n    maxAttempts: row.max_attempts,","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/generation-jobs.ts#L406-L442","documentation":"When transitioning a job back to 'queued' (a retry), assertValidJobStatusTransition rejects it if current.attempts >= current.maxAttempts. This is the retry-specific counterpart to the max-attempts processing cap, ensuring exhausted jobs cannot be re-queued indefinitely.","triggerScenarios":"retryGenerationJob or a manual transitionStatus(jobId, 'queued') call on a job that has already consumed all its attempts (attempts >= maxAttempts).","commonSituations":"User clicking 'retry' in a UI after the job exhausted retries; automated retry handlers ignoring the attempts counter; maxAttempts set to 1 so any single failure permanently blocks requeueing.","solutions":["Check attempts < maxAttempts before calling retry/transition to 'queued'","Create a fresh generation job rather than retrying an exhausted one","Increase maxAttempts at job creation if more retry headroom is required","Resolve the root cause of prior failures (e.g. provider errors) before any requeue"],"exampleFix":"// before\nawait jobs.retryGenerationJob(jobId, projectId, teamId);\n// after\nconst job = await jobs.getByIdForScope(jobId, projectId, teamId);\nif (job.attempts < job.maxAttempts) {\n  await jobs.retryGenerationJob(jobId, projectId, teamId);\n} else {\n  await jobs.create({ ...sameSource, maxAttempts: job.maxAttempts + 1 });\n}","handlingStrategy":"validation","validationCode":"if (nextStatus === 'queued' && job.attempts >= job.maxAttempts) {\n  throw new Error('retry budget exhausted');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await jobs.retryGenerationJob(jobId, projectId, teamId);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('cannot retry observation generation job after max_attempts')) {\n    // surface 'retries exhausted' to the user or spawn a new job\n    return notifyRetriesExhausted(jobId);\n  }\n  throw err;\n}","preventionTips":["Disable retry UI/actions once attempts >= maxAttempts","Prefer creating a new job over requeueing exhausted ones","Increase maxAttempts at creation time when long retry budgets are needed","Investigate and fix underlying failures before any requeue"],"tags":["retry","state-machine","postgres"],"backgroundTag":"invalid-state-transition","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"}