thedotmack/claude-mem · error · Error
illegal observation generation job transition from
Error message
illegal observation generation job transition from ${current.status} to ${nextStatus} What it means
assertValidJobStatusTransition validates the requested transition against ALLOWED_JOB_TRANSITIONS, a whitelist of legal status paths for observation generation jobs. When the current status does not allow jumping to nextStatus, this error is thrown. It enforces a well-defined lifecycle (e.g. queued -> processing -> completed) rather than arbitrary status changes.
Solutions
- Follow the documented status lifecycle: only transition to statuses in ALLOWED_JOB_TRANSITIONS[current.status]
- Read the job's current status first and compute the correct next step
- Serialize status updates (single worker / row lock) to avoid conflicting transitions
- Check ALLOWED_JOB_TRANSITIONS in the source to confirm which transitions are legal before coding the flow
Example fix
// before await jobs.transitionStatus(jobId, 'completed'); // job is 'queued' // after await jobs.transitionStatus(jobId, 'processing'); await jobs.transitionStatus(jobId, 'completed');
Defensive patterns
Strategy: validation
Validate before calling
const next = allowedNextStatuses[current.status];
if (!next.includes(wanted)) throw new Error(`illegal transition ${current.status} -> ${wanted}`); Try / catch
try {
await jobs.transitionStatus(jobId, next);
} catch (err) {
if (err instanceof Error && err.message.startsWith('illegal observation generation job transition')) {
// refetch current status and recompute the correct next step
const job = await jobs.getByIdForScope(jobId, projectId, teamId);
return recomputeAndTransition(job);
}
throw err;
} Prevention
- Mirror ALLOWED_JOB_TRANSITIONS client-side and validate before calling
- Always read fresh job status (avoid stale caches) before transitioning
- Serialize status changes per job to avoid racing workers
- Write integration tests covering the full lifecycle
When it happens
Trigger: transitionStatus calls like queued -> completed, completed -> queued (on a non-terminal-but-completed variant), processing -> queued depending on the map, or any out-of-order sequence not listed in ALLOWED_JOB_TRANSITIONS.
Common situations: Application code trying to short-circuit the lifecycle (skip processing, go straight to completed); custom retry logic setting states out of order; two workers updating status concurrently in conflicting orders; version drift after schema/status enumeration changes.
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…
- cannot transition observation generation job from terminal…
- 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/6acc946bc734ae11.
Report an issue: GitHub.
Appendix: source
Thrown at src/storage/postgres/generation-jobs.ts:416
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,
projectId: row.project_id,
teamId: row.team_id,
agentEventId: row.agent_event_id,
sourceType: row.source_type,View on GitHub (pinned to d8bc9755e7)