thedotmack/claude-mem · error
observation generation job status transition was not applied
Error message
observation generation job status transition was not applied
What it means
Thrown by transitionStatus when the UPDATE ... RETURNING matched zero rows, yet the job row exists (current found) and assertValidJobStatusTransition did not reject. The UPDATE's WHERE clause enforces extra guards the validator doesn't: status must be 'queued' or 'processing', the target must be an allowed transition, AND attempts < max_attempts. So a logically-valid transition that the DB rejected (typically because the retry budget is exhausted: attempts >= max_attempts) hits this 'should-not-happen-per-validator' branch.
Source
Thrown at src/storage/postgres/generation-jobs.ts:223
input.lastError == null ? null : JSON.stringify(input.lastError),
input.projectId,
input.teamId
]
);
if (row) {
return mapJobRow(row);
}
const current = await queryOne<JobRow>(
this.client,
'SELECT * FROM observation_generation_jobs WHERE id = $1 AND project_id = $2 AND team_id = $3',
[input.id, input.projectId, input.teamId]
);
if (!current) {
return null;
}
assertValidJobStatusTransition(mapJobRow(current), input.status);
throw new Error('observation generation job status transition was not applied');
}
async listByStatusForScope(input: {
status: ObservationGenerationJobStatus;
projectId: string;
teamId: string;
limit?: number;
}): Promise<PostgresObservationGenerationJob[]> {
const result = await this.client.query<JobRow>(
`
SELECT * FROM observation_generation_jobs
WHERE status = $1 AND project_id = $2 AND team_id = $3
ORDER BY created_at ASC
LIMIT $4
`,
[input.status, input.projectId, input.teamId, input.limit ?? 100]
);
return result.rows.map(mapJobRow);View on GitHub (pinned to d768ba3643)
Solutions
- Check the job's attempts vs max_attempts before transitioning; stop retrying once exhausted.
- Ensure you only transition jobs currently in 'queued' or 'processing' (re-fetch status before transitioning).
- Guard against concurrent transitions with locking/lockedBy so two workers don't both act on a stale status.
- If the validator is more permissive than the DB guard (a bug), tighten assertValidJobStatusTransition to match the WHERE clause so callers get a clearer error.
Example fix
// before
await repo.transitionStatus({ id, projectId, teamId, status: 'processing' });
// after
const job = await repo.findById(id, projectId, teamId);
if (!job || job.attempts >= job.maxAttempts) {
throw new Error('Job retry budget exhausted');
}
if (job.status !== 'queued' && job.status !== 'processing') {
throw new Error(`Cannot transition from terminal status ${job.status}`);
}
await repo.transitionStatus({ id, projectId, teamId, status: 'processing' }); Defensive patterns
Strategy: validation
Validate before calling
// Re-fetch and check DB-level guards before transitioning
const job = await repo.findById(input.id, input.projectId, input.teamId);
if (!job) throw new Error('Job not found');
if (job.attempts >= job.maxAttempts) throw new Error('Retry budget exhausted');
if (job.status !== 'queued' && job.status !== 'processing') {
throw new Error(`Cannot transition from ${job.status}`);
}
await repo.transitionStatus(input); Try / catch
try {
const updated = await repo.transitionStatus(input);
if (!updated) throw new Error('Transition not applied');
} catch (err) {
if (err instanceof Error && /status transition was not applied/.test(err.message)) {
// job likely terminal or budget exhausted; re-fetch and handle accordingly
const current = await repo.findById(input.id, input.projectId, input.teamId);
logger.warn('JOBS', 'Transition rejected by DB guard', { current });
}
throw err;
} Prevention
- Check attempts < max_attempts and current status (queued/processing) before transitioning.
- Use lockedBy/locking to prevent concurrent workers racing on one job.
- Align assertValidJobStatusTransition with the UPDATE WHERE guards so callers get the right error.
When it happens
Trigger: transitionStatus called to move a job to 'processing' or 'queued' when the job has already consumed attempts >= max_attempts, or its current status isn't 'queued'/'processing' (e.g., already completed/cancelled). The validator passes but the guarded UPDATE matches nothing.
Common situations: Retrying a job that exceeded max_attempts; concurrent workers racing on the same job where one already moved it out of queued/processing; caller requesting a transition from a terminal status (completed/failed/cancelled) which the validator allows but the DB guard rejects.
Related errors
- agent_event source_id must belong to project_id and team_id
- CLAUDE_MEM_SERVER_DATABASE_URL is required for `server ${com
- Postgres requires CLAUDE_MEM_SERVER_DATABASE_URL
- cannot transition observation generation job from terminal s
- illegal observation generation job transition from ${current
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/5d7816521666a72b.
Report an issue: GitHub.