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

  1. Follow the documented status lifecycle: only transition to statuses in ALLOWED_JOB_TRANSITIONS[current.status]
  2. Read the job's current status first and compute the correct next step
  3. Serialize status updates (single worker / row lock) to avoid conflicting transitions
  4. 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

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


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)