immich-app/immich · error · ImmichStartupError

Failed to add job handler for

Error message

Failed to add job handler for ${label}

What it means

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.

Solutions

  1. Search the codebase for the duplicated decorator: grep -rn "JobName.<NAME>" and remove one of the two @OnJob registrations
  2. If you intended a new job, create a distinct JobName enum entry and queue for the new handler
  3. Check recent merge/rebase changes that may have reintroduced a moved handler in both old and new services
  4. Rebuild/restart to confirm the duplicate isn't a stale-build artifact (clean dist and rerun)

Example fix

// before (two handlers for one job)
@OnJob({ name: JobName.TestMigration, queue: QueueName.Migration })
async handleA() {...}
// service B
@OnJob({ name: JobName.TestMigration, queue: QueueName.Migration })
async handleB() {...}
// after — keep exactly one
@OnJob({ name: JobName.TestMigration, queue: QueueName.Migration })
async handleA() {...}
Defensive patterns

Strategy: validation

Validate before calling

const names = services.flatMap((s) => getOnJobNames(s));
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
if (dupes.length > 0) throw new Error(`duplicate JobName handlers: ${dupes.join(', ')}`);

Try / catch

try {
  jobRepo.setup({ services });
} catch (e) {
  if (e instanceof ImmichStartupError && (e.message as string).startsWith('Failed to add job handler')) {
    logger.fatal(e.message); // fail fast; duplicate registration is a code defect
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

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

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/632c0c348cb4825e. Report an issue: GitHub.

Appendix: 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 e55ac299a4)