thedotmack/claude-mem · error

cannot process observation generation job after max_attempts

Error message

cannot process observation generation job after max_attempts is reached

What it means

Thrown by assertValidJobStatusTransition when nextStatus is 'processing' and current.attempts >= current.maxAttempts. The job has exhausted its retry budget; no more processing attempts are permitted. The UPDATE statement also enforces this (attempts < max_attempts), so this guard explains a zero-row update.

Source

Thrown at src/storage/postgres/generation-jobs.ts:411

  completed: [],
  failed: [],
  cancelled: []
};

function assertValidJobStatusTransition(
  current: PostgresObservationGenerationJob,
  nextStatus: ObservationGenerationJobStatus
): void {
  if (TERMINAL_JOB_STATUSES.has(current.status)) {
    throw new Error(`cannot transition observation generation job from terminal status ${current.status}`);
  }

  if (!ALLOWED_JOB_TRANSITIONS[current.status].includes(nextStatus)) {
    throw new Error(`illegal observation generation job transition from ${current.status} to ${nextStatus}`);
  }

  if (nextStatus === 'processing' && current.attempts >= current.maxAttempts) {
    throw new Error('cannot process observation generation job after max_attempts is reached');
  }

  if (nextStatus === 'queued' && current.attempts >= current.maxAttempts) {
    throw new Error('cannot retry observation generation job after max_attempts is reached');
  }
}

function mapJobRow(row: JobRow): PostgresObservationGenerationJob {
  return {
    id: row.id,
    projectId: row.project_id,
    teamId: row.team_id,
    agentEventId: row.agent_event_id,
    sourceType: row.source_type,
    sourceId: row.source_id,
    serverSessionId: row.server_session_id,
    jobType: row.job_type,
    status: row.status,

View on GitHub (pinned to d768ba3643)

Solutions

  1. Move the job to 'failed' (allowed from both queued and processing) once attempts reach maxAttempts.
  2. If more retries are genuinely needed, create a new job with a higher maxAttempts.
  3. Have the worker check job.attempts < job.maxAttempts before claiming processing.

Example fix

// before
await jobs.transitionStatus({ id, projectId, teamId, status:'processing' });
// after
if (job.attempts >= job.maxAttempts) {
  await jobs.transitionStatus({ id, projectId, teamId, status:'failed', lastError:{ reason:'max_attempts' } });
  return;
}
await jobs.transitionStatus({ id, projectId, teamId, status:'processing' });
Defensive patterns

Strategy: validation

Validate before calling

if (job.attempts >= job.maxAttempts) { await jobs.transitionStatus({ id, projectId, teamId, status:'failed', lastError:{reason:'max_attempts'} }); return; }

Type guard

function canProcess(job){ return job.status!=='processing' && job.attempts < job.maxAttempts; }

Prevention

When it happens

Trigger: Calling transitionStatus({status:'processing'}) after the job already attempted maxAttempts times. Default maxAttempts is 3 unless overridden in create().

Common situations: Worker retry loop not checking attempt count; bullmq retries exceeding the DB max_attempts; a long-stuck queued job being picked up after prior failures pushed attempts over the limit.

Related errors


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