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
- 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.
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
- 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.
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
- Failed to find job handler for Job.${jobKey} ("${jobName}")
- Unable to determine worker type
- Not in maintenance mode
- Failed to read helmet file: ${helmetFile}
- Invalid environment variables: \n - [${path}] ${issue.messa
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/632c0c348cb4825e.
Report an issue: GitHub.