immich-app/immich · error · ImmichStartupError
Failed to find job handler for Job.
Error message
Failed to find job handler for Job.${jobKey} ("${jobName}") What it means
After handler discovery, the job repository validates that every @OnJob-decorated item's job name has a registered handler. If an item references a JobName for which no handler exists (handlers are keyed by the resolved job name), setup throws an ImmichStartupError telling the developer to add the @OnJob decorator — this guards against jobs declared but never implemented.
Solutions
- Add the missing handler method decorated with @OnJob({ name: JobName.<KEY>, queue: QueueName.XYZ }) as the error message suggests
- Check for a JobName rename/mismatch: ensure the decorator name matches an enum value and the handler still exists after refactors
- Resolve any earlier 'Failed to add job handler' duplicates so the handler registers correctly
- Rebuild after changes (clean dist) to make sure the running code includes the new handler
Example fix
// before — job referenced but never handled
@OnJob({ name: JobName.QueueSync, queue: QueueName.Backup })
// no method anywhere handles JobName.QueueSync
// after
export class BackupService {
@OnJob({ name: JobName.QueueSync, queue: QueueName.Backup })
async handleQueueSync(job: Job<QueueSyncParameters>) {
// ...
}
} Defensive patterns
Strategy: validation
Validate before calling
const declared = new Set<string>(getAllJobNamesReferencedByDecorators());
const handled = new Set<string>(Object.keys(jobHandlers));
const missing = [...declared].filter((n) => !handled.has(n));
if (missing.length > 0) throw new Error(`jobs without handlers: ${missing.join(', ')}`); Try / catch
try {
jobRepo.setup({ services });
} catch (e) {
if (e instanceof ImmichStartupError && (e.message as string).startsWith('Failed to find job handler')) {
logger.fatal(`${e.message} — add the missing @OnJob handler before deploying.`);
}
throw e;
} Prevention
- Pair every new JobName enum value with its handler in the same PR
- Run a CI test enumerating JobName and asserting each has a decorator+handler
- Rebuild dist after renames so stale compiled code doesn't reference old job names
When it happens
Trigger: A @OnJob decorator uses a JobName whose handler registration was skipped or failed, or a job key was renamed/mistyped so the handlers map (keyed by string job name) has no entry for it — e.g. decorator name and enum value diverge after a refactor.
Common situations: Adding a new JobName enum value and a decorator but forgetting the handler method; renaming a JobName in one place only; a duplicated-handler error earlier preventing registration while the item still exists; typos in the decorator's name string.
Related errors
- Failed to add job handler for
- Invalid environment variables: \n
- Unable to determine worker type
- Could not find asset
- Detected an inconsistent media location. For more…
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/5f99d458086bc6f1.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/repositories/job.repository.ts:85
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}")`;
this.logger.error(
`${errorMessage}. Make sure to add the @OnJob({ name: JobName.${jobKey}, queue: QueueName.XYZ }) decorator for the new job.`,
);
throw new ImmichStartupError(errorMessage);
}
}
}
startWorkers() {
const { bull } = this.configRepository.getEnv();
for (const queueName of Object.values(QueueName)) {
this.logger.debug(`Starting worker for queue: ${queueName}`);
this.workers[queueName] = new Worker(
queueName,
(job) => this.eventRepository.emit('JobRun', queueName, job as JobItem),
{ ...bull.config, concurrency: 1, name: ImmichWorker.Microservices },
);
}
}
watchWorkers() {
this.workerWatcher ??= setInterval(() => void this.checkWorkers(), WORKER_WATCH_INTERVAL_MS);View on GitHub (pinned to e55ac299a4)