immich-app/immich · critical · ImmichStartupError

Failed to add job handler for ${label}

Error message

Failed to add job handler for ${label}

What it means

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.

Source

Thrown at server/src/repositories/job.repository.ts:63

      const instance = this.moduleRef.get<any>(Service);
      for (const methodName of getMethodNames(instance)) {
        const handler = instance[methodName];
        const config = reflector.get<JobConfig>(MetadataKey.JobConfig, handler);
        if (!config) {
          continue;
        }

        const { name: jobName, queue: queueName } = config;
        const label = `${Service.name}.${handler.name}`;

        // one handler per job
        if (Object.hasOwn(this.handlers, jobName)) {
          const jobKey = getKeyByValue(JobName, jobName);
          const errorMessage = `Failed to add job handler for ${label}`;
          this.logger.error(
            `${errorMessage}. JobName.${jobKey} is already handled by ${this.handlers[jobName]!.label}.`,
          );
          throw new ImmichStartupError(errorMessage);
        }

        this.handlers[jobName] = {
          label,
          jobName,
          queueName,
          handler: handler.bind(instance),
        };

        this.logger.verbose(`Added job handler: ${jobName} => ${label}`);
      }
    }

    // no missing handlers
    for (const [jobKey, jobName] of Object.entries(JobName)) {
      const item = this.handlers[jobName];
      if (!item) {
        const errorMessage = `Failed to find job handler for Job.${jobKey} ("${jobName}")`;

View on GitHub (pinned to 199723261c)

Solutions

  1. Read the log line: it names the duplicate JobName and the existing handler label already registered.
  2. Remove or rename the duplicate @OnJob decorator so only one method handles that JobName.
  3. Rebuild and restart; the startup check will pass once the conflict is gone.
Defensive patterns

Strategy: validation

Validate before calling

// in tests, assert no two OnJob decorators share a JobName
const counts = new Map<string, number>();
for (const meta of collectJobDecorators()) counts.set(meta.name, (counts.get(meta.name) ?? 0) + 1);
const dupes = [...counts.entries()].filter(([, c]) => c > 1);
if (dupes.length) throw new Error(`Duplicate job handlers: ${JSON.stringify(dupes)}`);

Prevention

When it happens

Trigger: Two methods across services share the same @OnJob({ name: JobName.X }), or a fork/branch accidentally duplicated a handler registration.

Common situations: 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.

Related errors


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