{"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":"exception","errorClass":"ImmichStartupError","httpStatus":null,"severity":"critical","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/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/repositories/job.repository.ts#L45-L81","documentation":"During JobRepository.setup, each @OnJob({ name, queue })-decorated method becomes a job handler keyed by JobName. The repository enforces one handler per JobName; if a second method tries to register the same JobName, it logs the conflict and throws ImmichStartupError('Failed to add job handler for <Service>.<method>'). This is a programming/deployment error, not a runtime condition.","triggerScenarios":"Two methods across services share the same @OnJob({ name: JobName.X }), or a fork/branch accidentally duplicated a handler registration.","commonSituations":"Merge conflict that leaves two handlers for one job; refactoring that moved a handler without removing the original; a custom plugin/fork adding a handler already defined upstream.","solutions":["Read the log line: it names the duplicate JobName and the existing handler label already registered.","Remove or rename the duplicate @OnJob decorator so only one method handles that JobName.","Rebuild and restart; the startup check will pass once the conflict is gone."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// in tests, assert no two OnJob decorators share a JobName\nconst counts = new Map<string, number>();\nfor (const meta of collectJobDecorators()) counts.set(meta.name, (counts.get(meta.name) ?? 0) + 1);\nconst dupes = [...counts.entries()].filter(([, c]) => c > 1);\nif (dupes.length) throw new Error(`Duplicate job handlers: ${JSON.stringify(dupes)}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Maintain a single source of truth for each JobName handler.","Add a test that fails when two methods declare the same @OnJob name.","Review decorator usage during merge conflict resolution."],"tags":["jobs","bullmq","startup","nest","decorators","duplicate"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}