thedotmack/claude-mem · error
cannot transition observation generation job from terminal s
Error message
cannot transition observation generation job from terminal status ${current.status} What it means
Thrown by assertValidJobStatusTransition (reached from transitionStatus after the UPDATE matches zero rows). The job's current status is in TERMINAL_JOB_STATUSES (completed, failed, cancelled), which all have empty ALLOWED_JOB_TRANSITIONS. Terminal jobs are immutable, so any further transition is rejected.
Source
Thrown at src/storage/postgres/generation-jobs.ts:403
return { agentEventId: null, serverSessionId: input.serverSessionId ?? null };
}
const TERMINAL_JOB_STATUSES = new Set<ObservationGenerationJobStatus>(['completed', 'failed', 'cancelled']);
const ALLOWED_JOB_TRANSITIONS: Record<ObservationGenerationJobStatus, readonly ObservationGenerationJobStatus[]> = {
queued: ['processing', 'failed', 'cancelled'],
processing: ['queued', 'completed', 'failed', 'cancelled'],
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,View on GitHub (pinned to d768ba3643)
Solutions
- Before calling transitionStatus, load the job and return early if its status is in {completed, failed, cancelled}.
- Make the caller idempotent: treat a terminal status matching the desired outcome as success.
- If you genuinely need to redo the work, create a new job (new source/idempotency key) instead of reopening a terminal one.
Example fix
// before
await jobs.transitionStatus({ id, projectId, teamId, status:'completed' });
// after
const current = await jobs.getByIdForScope({ id, projectId, teamId });
if (current && ['completed','failed','cancelled'].includes(current.status)) {
return current; // already terminal, nothing to do
}
await jobs.transitionStatus({ id, projectId, teamId, status:'completed' }); Defensive patterns
Strategy: validation
Validate before calling
const TERMINAL = new Set(['completed','failed','cancelled']);
const job = await jobs.getByIdForScope({ id, projectId, teamId });
if (job && TERMINAL.has(job.status)) return job; Type guard
function isTerminalJob(job) { return new Set(['completed','failed','cancelled']).has(job?.status); } Try / catch
try { await jobs.transitionStatus({...}); } catch (e) { if (e.message.startsWith('cannot transition observation generation job from terminal status')) { return; } throw e; } Prevention
- Design job callers to be idempotent on terminal state.
- Never reopen a terminal job; start a new one.
When it happens
Trigger: Calling transitionStatus on a job whose status is already 'completed', 'failed', or 'cancelled'. The UPDATE WHERE clause already excludes terminal statuses, so the function re-reads the row and this guard explains why nothing was applied.
Common situations: Duplicate worker acknowledgements racing after a job finished; a retry worker that does not check terminal state; UI replaying a transition button; idempotency logic not short-circuiting on terminal jobs.
Related errors
- illegal observation generation job transition from ${current
- cannot process observation generation job after max_attempts
- cannot retry observation generation job after max_attempts i
- server_session_id must match the agent_event server_session_
- session_summary source_id must equal server_session_id
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/8c762e383ab1e89f.
Report an issue: GitHub.