thedotmack/claude-mem · error · Error
cannot retry observation generation job after max_attempts…
Error message
cannot retry observation generation job after max_attempts is reached
What it means
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.
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
Example fix
// before
await jobs.retryGenerationJob(jobId, projectId, teamId);
// after
const job = await jobs.getByIdForScope(jobId, projectId, teamId);
if (job.attempts < job.maxAttempts) {
await jobs.retryGenerationJob(jobId, projectId, teamId);
} else {
await jobs.create({ ...sameSource, maxAttempts: job.maxAttempts + 1 });
} Defensive patterns
Strategy: validation
Validate before calling
if (nextStatus === 'queued' && job.attempts >= job.maxAttempts) {
throw new Error('retry budget exhausted');
} Try / catch
try {
await jobs.retryGenerationJob(jobId, projectId, teamId);
} catch (err) {
if (err instanceof Error && err.message.includes('cannot retry observation generation job after max_attempts')) {
// surface 'retries exhausted' to the user or spawn a new job
return notifyRetriesExhausted(jobId);
}
throw err;
} Prevention
- 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
When it happens
Trigger: retryGenerationJob or a manual transitionStatus(jobId, 'queued') call on a job that has already consumed all its attempts (attempts >= maxAttempts).
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot process observation generation job after…
- cannot transition observation generation job from terminal…
- illegal observation generation job transition from
- observation generation job status transition was not applied
- agent_event_id must belong to project_id and team_id
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/a023ce5aafeded3b.
Report an issue: GitHub.
Appendix: source
Thrown at src/storage/postgres/generation-jobs.ts:424
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,
idempotencyKey: row.idempotency_key,
bullmqJobId: row.bullmq_job_id,
attempts: row.attempts,
maxAttempts: row.max_attempts,View on GitHub (pinned to d8bc9755e7)