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

  1. GET /admin/queues/{name} and check statistics.active before issuing start.
  2. Disable the Start button client-side while isActive is true.
  3. 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

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


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/c97867a37edd172e. Report an issue: GitHub.