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
- 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)
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
- 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
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
- Failed to find job handler for Job.
- Invalid environment variables: \n
- Unable to determine worker type
- A tag with that name already exists
- Cannot merge a person into themselves
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)