immich-app/immich · warning · BadRequestException
Job is already running
Error message
Job is already running
What it means
Thrown by QueueService.start (the private handler invoked when a queue is started) when jobRepository.isActive(name) is true. Prevents two overlapping full-queue passes (e.g. re-encoding all videos twice). BadRequestException -> HTTP 400.
Source
Thrown at server/src/services/queue.service.ts:190
async emptyQueue(auth: AuthDto, name: QueueName, dto: QueueDeleteDto) {
await this.jobRepository.empty(name);
if (dto.failed) {
await this.jobRepository.clear(name, QueueCleanType.Failed);
}
}
private async getByName(name: QueueName): Promise<QueueResponseDto> {
const [statistics, isPaused] = await Promise.all([
this.jobRepository.getJobCounts(name),
this.jobRepository.isPaused(name),
]);
return { name, isPaused, statistics };
}
private async start(name: QueueName, { force }: QueueCommandDto): Promise<void> {
const isActive = await this.jobRepository.isActive(name);
if (isActive) {
throw new BadRequestException(`Job is already running`);
}
await this.eventRepository.emit('QueueStart', { name });
switch (name) {
case QueueName.VideoConversion: {
return this.jobRepository.queue({ name: JobName.AssetEncodeVideoQueueAll, data: { force } });
}
case QueueName.StorageTemplateMigration: {
return this.jobRepository.queue({ name: JobName.StorageTemplateMigration });
}
case QueueName.Migration: {
return this.jobRepository.queue({ name: JobName.FileMigrationQueueAll });
}
case QueueName.SmartSearch: {View on GitHub (pinned to 199723261c)
Solutions
- GET /admin/queues/{name} and check statistics.active before issuing start.
- Disable the Start button client-side while isActive is true.
- For forced restarts, pause+empty the queue first, then start.
Example fix
// before
const isActive = await this.jobRepository.isActive(name);
if (isActive) {
throw new BadRequestException(`Job is already running`);
}
// client-side guard
const q = await api.get(`/admin/queues/${name}`);
if (q.statistics.active > 0) return; // already running
await api.post(`/admin/queues/${name}/start`, { force: false }); Defensive patterns
Strategy: validation
Validate before calling
// Check active state before starting a queue.
const q = await queueService.get(auth, name);
if (q.statistics.active > 0 || q.isActive) {
// already running, do not call start
return q;
}
await queueService.handleCommand(auth, name, { command: 'start', force: false }); Type guard
const isQueueIdle = (q: QueueResponseDto): boolean => !q.isPaused && q.statistics.active === 0;
Try / catch
try {
await queueService.handleCommand(auth, name, { command: 'start', force });
} catch (e) {
if (e instanceof BadRequestException && /already running/i.test(e.message)) {
// benign: nothing to do
return;
}
throw e;
} Prevention
- Poll GET /admin/queues/{name} before issuing start.
- Disable the Start button while statistics.active > 0.
- Treat 'already running' as success in idempotent start flows.
When it happens
Trigger: POST /admin/queues/{name}/start (or the queue command endpoint) while the same queue already has an active job pass; double-click on 'Start' in the admin Jobs UI.
Common situations: User clicks Start repeatedly; an external automation triggers a queue start without checking state first; a long-running queue (e.g. video conversion) is still processing when a new start is requested.
Related errors
- The BackgroundTask queue cannot be paused
- Invalid job name: ${name}
- API Key not found
- Forbidden
- Invalid job name
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/c97867a37edd172e.
Report an issue: GitHub.