{"record":{"id":"7a41a199162fb032","repo":"thedotmack/claude-mem","slug":"cannot-process-observation-generation-job-after-ma","errorCode":null,"errorMessage":"cannot process observation generation job after max_attempts is reached","messagePattern":"cannot process observation generation job after max_attempts is reached","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/storage/postgres/generation-jobs.ts","lineNumber":420,"sourceCode":"  completed: [],\n  failed: [],\n  cancelled: []\n};\n\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,","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/storage/postgres/generation-jobs.ts#L402-L438","documentation":"When transitioning a job to 'processing', assertValidJobStatusTransition rejects the change if current.attempts >= current.maxAttempts. This caps how many times a job may be picked up for processing, preventing infinite processing loops on a permanently failing job.","triggerScenarios":"transitionStatus(jobId, 'processing') on a job whose attempts counter has reached maxAttempts — e.g. a worker claiming a job again after maxAttempts failed attempts, or a buggy claim loop not incrementing checks.","commonSituations":"Cron/poll worker repeatedly retrying a poison job; queue re-delivery after visibility timeout when attempts were already exhausted; maxAttempts configured lower than the number of workers' retry rounds.","solutions":["Let the job fail permanently (transition to a failed/terminal status) instead of processing again","Increase maxAttempts when creating the job if more retries are legitimately needed","Inspect why each attempt failed before re-queuing; fix the underlying generation error first","Guard worker claim logic: only transition to processing if attempts < maxAttempts"],"exampleFix":"// before\nif (job.status === 'queued') await jobs.transitionStatus(job.id, 'processing');\n// after\nif (job.status === 'queued' && job.attempts < job.maxAttempts) {\n  await jobs.transitionStatus(job.id, 'processing');\n} else {\n  await jobs.transitionStatus(job.id, 'failed');\n}","handlingStrategy":"validation","validationCode":"if (nextStatus === 'processing' && job.attempts >= job.maxAttempts) {\n  throw new Error('attempts exhausted');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await jobs.transitionStatus(jobId, 'processing');\n} catch (err) {\n  if (err instanceof Error && err.message.includes('max_attempts is reached')) {\n    await jobs.transitionStatus(jobId, 'failed');\n    return;\n  }\n  throw err;\n}","preventionTips":["Check attempts vs maxAttempts in the worker claim path","Set maxAttempts generously enough for transient provider failures","Alert on jobs exhausting attempts instead of looping them","Track failure reasons per attempt to fix root causes"],"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"}