immich-app/immich · error
Face not found
Error message
Face ${id} not found What it means
handleRecognizeFaces is a machine-learning job entry point that runs facial recognition for a single face id. Before doing so it loads the face together with its asset via personRepository.getFaceForFacialRecognitionJob(id). If no face row exists, or the face exists but its asset row is missing (orphaned), the job logs this warning and returns JobStatus.Failed rather than throwing.
Solutions
- Confirm the face id still exists (person.faces / asset joins) — if it was deleted, the Failed status is expected and can be ignored.
- Clear stale queued ML jobs (drain/restart the ML job queue) so dead face ids are not reprocessed.
- If assets are orphaned, repair the database so faces reference valid assets (re-run smart-search/facial-recognition pipelines from the admin UI).
- Check sourceType: faces not from MachineLearning source are skipped intentionally — verify the face was produced by the ML pipeline.
Defensive patterns
Strategy: validation
Validate before calling
// before enqueuing recognition for a face id
const face = await personRepository.getFaceForFacialRecognitionJob(id);
if (!face || !face.asset) {
logger.warn(`Face ${id} not found; skipping enqueue`);
} else if (face.sourceType !== SourceType.MachineLearning) {
logger.warn(`Face ${id} has non-ML source; skipping`);
} else {
await queue.add({ name: JobName.FacialRecognition, data: { faceId: id } });
} Type guard
const isRecognizableFace = ( f: Awaited<ReturnType<typeof personRepository.getFaceForFacialRecognitionJob>>, ): f is FaceWithAsset => !!f && !!f.asset;
Try / catch
const status = await handleRecognizeFaces({ id });
if (status === JobStatus.Failed) {
logger.warn(`Face ${id} recognition failed (face or asset missing); dropping job`);
} Prevention
- Drain the ML job queue after bulk face deletions or 'reset faces' operations.
- After restoring a database, re-run facial recognition instead of replaying old queued jobs.
- Watch for orphaned faces (asset deleted) and clean them up with DB consistency checks.
- Only enqueue faces with sourceType MachineLearning.
When it happens
Trigger: A facial-recognition job enqueued with a face id whose row was deleted between enqueue and processing, or whose joined asset row is missing; queueing recognition for faces created by a since-rolled-back or partially-failed ML sync.
Common situations: Faces removed by a 'reset recognized faces' operation while jobs were still queued; database restores or migrations that left faces without matching assets; stale queued jobs after a library/asset deletion.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Asset dimensions are not available for editing
- Asset not in stack
- Cannot remove stack's primary asset
- Crop action must be the first edit action
- Crop parameters are out of bounds
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/5b3e348c3ad9d45e.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/person.service.ts:490
batch.map((face) => ({ name: JobName.FacialRecognition, data: { id: face.id, deferred: false } })),
);
}
await this.systemMetadataRepository.set(SystemMetadataKey.FacialRecognitionState, { lastRun });
return JobStatus.Success;
}
@OnJob({ name: JobName.FacialRecognition, queue: QueueName.FacialRecognition })
async handleRecognizeFaces({ id, deferred }: JobOf<JobName.FacialRecognition>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.Skipped;
}
const face = await this.personRepository.getFaceForFacialRecognitionJob(id);
if (!face || !face.asset) {
this.logger.warn(`Face ${id} not found`);
return JobStatus.Failed;
}
if (face.sourceType !== SourceType.MachineLearning) {
this.logger.warn(`Skipping face ${id} due to source ${face.sourceType}`);
return JobStatus.Skipped;
}
if (!face.faceSearch?.embedding) {
this.logger.warn(`Face ${id} does not have an embedding`);
return JobStatus.Failed;
}
if (face.personGroupId) {
this.logger.debug(`Face ${id} already has a person assigned`);
return JobStatus.Skipped;
}
View on GitHub (pinned to f48d4b3321)