{"record":{"id":"c97867a37edd172e","repo":"immich-app/immich","slug":"job-is-already-running","errorCode":null,"errorMessage":"Job is already running","messagePattern":"Job is already running","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/queue.service.ts","lineNumber":190,"sourceCode":"  async emptyQueue(auth: AuthDto, name: QueueName, dto: QueueDeleteDto) {\n    await this.jobRepository.empty(name);\n    if (dto.failed) {\n      await this.jobRepository.clear(name, QueueCleanType.Failed);\n    }\n  }\n\n  private async getByName(name: QueueName): Promise<QueueResponseDto> {\n    const [statistics, isPaused] = await Promise.all([\n      this.jobRepository.getJobCounts(name),\n      this.jobRepository.isPaused(name),\n    ]);\n    return { name, isPaused, statistics };\n  }\n\n  private async start(name: QueueName, { force }: QueueCommandDto): Promise<void> {\n    const isActive = await this.jobRepository.isActive(name);\n    if (isActive) {\n      throw new BadRequestException(`Job is already running`);\n    }\n\n    await this.eventRepository.emit('QueueStart', { name });\n\n    switch (name) {\n      case QueueName.VideoConversion: {\n        return this.jobRepository.queue({ name: JobName.AssetEncodeVideoQueueAll, data: { force } });\n      }\n\n      case QueueName.StorageTemplateMigration: {\n        return this.jobRepository.queue({ name: JobName.StorageTemplateMigration });\n      }\n\n      case QueueName.Migration: {\n        return this.jobRepository.queue({ name: JobName.FileMigrationQueueAll });\n      }\n\n      case QueueName.SmartSearch: {","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/queue.service.ts#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst isActive = await this.jobRepository.isActive(name);\nif (isActive) {\n  throw new BadRequestException(`Job is already running`);\n}\n\n// client-side guard\nconst q = await api.get(`/admin/queues/${name}`);\nif (q.statistics.active > 0) return; // already running\nawait api.post(`/admin/queues/${name}/start`, { force: false });","handlingStrategy":"validation","validationCode":"// Check active state before starting a queue.\nconst q = await queueService.get(auth, name);\nif (q.statistics.active > 0 || q.isActive) {\n  // already running, do not call start\n  return q;\n}\nawait queueService.handleCommand(auth, name, { command: 'start', force: false });","typeGuard":"const isQueueIdle = (q: QueueResponseDto): boolean =>\n  !q.isPaused && q.statistics.active === 0;","tryCatchPattern":"try {\n  await queueService.handleCommand(auth, name, { command: 'start', force });\n} catch (e) {\n  if (e instanceof BadRequestException && /already running/i.test(e.message)) {\n    // benign: nothing to do\n    return;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["queue","jobs","concurrency","admin","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}