thedotmack/claude-mem · error · Error
cannot transition observation generation job from terminal…
Error message
cannot transition observation generation job from terminal status ${current.status} What it means
assertValidJobStatusTransition guards the state machine for observation generation jobs. Once a job reaches a terminal status (in TERMINAL_JOB_STATUSES, e.g. completed/failed/cancelled), no further transitions are permitted. Thrown by transitionStatus when attempting to move a job out of a terminal state.
Solutions
- Check the job's current status before calling transitionStatus and skip if it is terminal
- Treat terminal-status errors as no-ops in retry/duplicate-processing paths (catch and log)
- Create a new generation job instead of trying to revive a terminal one
- Add idempotency guards (job version/attempt token) so duplicate updates are detected client-side
Example fix
// before
await jobs.transitionStatus(jobId, 'processing');
// after
const job = await jobs.getByIdForScope(jobId, projectId, teamId);
if (!TERMINAL_STATUSES.has(job.status)) {
await jobs.transitionStatus(jobId, 'processing');
} Defensive patterns
Strategy: try-catch
Validate before calling
if (TERMINAL_JOB_STATUSES.has(job.status)) return; // already terminal, nothing to do
Try / catch
try {
await jobs.transitionStatus(jobId, next);
} catch (err) {
if (err instanceof Error && err.message.startsWith('cannot transition observation generation job from terminal status')) {
return; // idempotent no-op
}
throw err;
} Prevention
- Check current status before every transition
- Make status updates idempotent — terminal transitions should be treated as success no-ops
- Debounce/dedupe completion callbacks in workers
- Never retry a terminal job; create a new one instead
When it happens
Trigger: Calling transitionStatus with a nextStatus (even 'processing', 'queued', or 'failed') on a job whose current status is already terminal — e.g. retrying a cancelled job or double-completing a finished job.
Common situations: Duplicate worker completion callbacks racing after the job already succeeded; retry logic firing on failed jobs without resetting them; a second API call to cancel an already-completed job; re-delivered queue messages processed twice.
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 retry observation generation job after max_attempts…
- 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/8c762e383ab1e89f.
Report an issue: GitHub.
Appendix: source
Thrown at src/storage/postgres/generation-jobs.ts:412
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 d8bc9755e7)