{"record":{"id":"632c0c348cb4825e","repo":"immich-app/immich","slug":"failed-to-add-job-handler-for-label","errorCode":null,"errorMessage":"Failed to add job handler for ${label}","messagePattern":"Failed to add job handler for (.+?)","errorType":"console","errorClass":"ImmichStartupError","httpStatus":null,"severity":"error","filePath":"server/src/repositories/job.repository.ts","lineNumber":63,"sourceCode":"      const instance = this.moduleRef.get<any>(Service);\n      for (const methodName of getMethodNames(instance)) {\n        const handler = instance[methodName];\n        const config = reflector.get<JobConfig>(MetadataKey.JobConfig, handler);\n        if (!config) {\n          continue;\n        }\n\n        const { name: jobName, queue: queueName } = config;\n        const label = `${Service.name}.${handler.name}`;\n\n        // one handler per job\n        if (Object.hasOwn(this.handlers, jobName)) {\n          const jobKey = getKeyByValue(JobName, jobName);\n          const errorMessage = `Failed to add job handler for ${label}`;\n          this.logger.error(\n            `${errorMessage}. JobName.${jobKey} is already handled by ${this.handlers[jobName]!.label}.`,\n          );\n          throw new ImmichStartupError(errorMessage);\n        }\n\n        this.handlers[jobName] = {\n          label,\n          jobName,\n          queueName,\n          handler: handler.bind(instance),\n        };\n\n        this.logger.verbose(`Added job handler: ${jobName} => ${label}`);\n      }\n    }\n\n    // no missing handlers\n    for (const [jobKey, jobName] of Object.entries(JobName)) {\n      const item = this.handlers[jobName];\n      if (!item) {\n        const errorMessage = `Failed to find job handler for Job.${jobKey} (\"${jobName}\")`;","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/repositories/job.repository.ts#L45-L81","documentation":"At startup, the job repository registers one handler per JobName from @OnJob-decorated methods. If two different services (or methods) attempt to register a handler for the same job name, the second registration throws an ImmichStartupError, because a job must have exactly one handler to keep queue processing deterministic.","triggerScenarios":"Two @OnJob({ name: JobName.X }) decorators exist for the same JobName — e.g. a service was duplicated, a new handler was added while the old one remained, or a job name constant was reused in a new decorator.","commonSituations":"Merge/rebase artifacts where the same handler was added twice; copying a job handler into a new service without removing the original; introducing a new JobName that aliases an existing one; conflicting code paths compiled together.","solutions":["Search the codebase for the duplicated decorator: grep -rn \"JobName.<NAME>\" and remove one of the two @OnJob registrations","If you intended a new job, create a distinct JobName enum entry and queue for the new handler","Check recent merge/rebase changes that may have reintroduced a moved handler in both old and new services","Rebuild/restart to confirm the duplicate isn't a stale-build artifact (clean dist and rerun)"],"exampleFix":"// before (two handlers for one job)\n@OnJob({ name: JobName.TestMigration, queue: QueueName.Migration })\nasync handleA() {...}\n// service B\n@OnJob({ name: JobName.TestMigration, queue: QueueName.Migration })\nasync handleB() {...}\n// after — keep exactly one\n@OnJob({ name: JobName.TestMigration, queue: QueueName.Migration })\nasync handleA() {...}","handlingStrategy":"validation","validationCode":"const names = services.flatMap((s) => getOnJobNames(s));\nconst dupes = names.filter((n, i) => names.indexOf(n) !== i);\nif (dupes.length > 0) throw new Error(`duplicate JobName handlers: ${dupes.join(', ')}`);","typeGuard":null,"tryCatchPattern":"try {\n  jobRepo.setup({ services });\n} catch (e) {\n  if (e instanceof ImmichStartupError && (e.message as string).startsWith('Failed to add job handler')) {\n    logger.fatal(e.message); // fail fast; duplicate registration is a code defect\n  }\n  throw e;\n}","preventionTips":["Before adding an @OnJob handler, grep for existing decorators with the same JobName","Write a startup test that instantiates the job repository over all services","When moving a handler between services, delete the original in the same commit"],"tags":["startup","jobs","duplicate","configuration"],"backgroundTag":"conflicting-config-options","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}