immich-app/immich · error · NotFoundException
Person not found
Error message
Person not found
What it means
In PersonService.getAll, when the client requests a closest-asset thumbnail (closestPersonId), the service resolves the person's face asset via getByGroupId and requires that both the person exists and it has a faceAssetId. If either is missing, it throws NotFoundException('Person not found') rather than returning a page with a broken thumbnail.
Solutions
- Refresh the people list (GET /api/people) and use a current person ID for closestPersonId.
- Assign a face to the person (pick a new feature face) if the person exists but has no faceAssetId.
- Verify the person belongs to the authenticated user; person IDs are per-owner.
- If the person was merged/deleted, update any saved references to the surviving person ID.
Example fix
// before
await api.peopleApi.getPeople({ withHidden: false, closestPersonId: oldPersonId });
// after
const people = await api.peopleApi.getPeople({});
const person = people.people.find((p) => p.id === oldPersonId);
if (person) {
await api.peopleApi.getPeople({ withHidden: false, closestPersonId: person.id });
} Defensive patterns
Strategy: validation
Validate before calling
const { people } = await api.peopleApi.getPeople({});
const person = people.find((p) => p.id === closestPersonId);
if (!person || !person.faceAssetId) {
throw new Error('Person missing or has no face asset — cannot request closest thumbnail');
} Type guard
function canRequestClosest(p) {
return typeof p?.id === 'string' && typeof p?.faceAssetId === 'string' && p.faceAssetId.length > 0;
} Try / catch
try {
await api.peopleApi.getPeople({ closestPersonId, size });
} catch (e) {
if (e.status === 404) {
const fresh = await api.peopleApi.getPeople({}); // re-sync IDs after merges/deletions
} else throw e;
} Prevention
- Re-fetch person IDs after any merge/delete; never persist closestPersonId long-term.
- Check faceAssetId is set before requesting the closest-person variant.
- Scope requests to the same authenticated owner.
When it happens
Trigger: GET /api/people with query parameters closestPersonId (and size) where the ID refers to a person that no longer exists, belongs to another user, or has no face asset assigned (e.g. person whose face was reassigned during a merge).
Common situations: Stale cached person IDs after merges/deletions, requesting a person from another account's scope, persons created without detected faces, or referencing a person that was hidden/merged away.
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 not found
- Not Found
- Asset media not found
- Invalid assetId for feature face or asset is offline
- Asset does not have valid dimensions
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/38c6a9e898e97490.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/person.service.ts:69
const personKey = ({ ownerId, personGroupId }: PersonId) => `${ownerId}/${personGroupId}`;
@Injectable()
export class PersonService extends BaseService {
async getAll(auth: AuthDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {
const { withHidden = false, closestAssetId, closestPersonId, page, size } = dto;
let closestFaceAssetId = closestAssetId;
const pagination = {
take: size,
skip: (page - 1) * size,
};
if (closestPersonId) {
const person = await this.personRepository.getByGroupId({
ownerId: auth.user.id,
personGroupId: closestPersonId,
});
if (!person?.faceAssetId) {
throw new NotFoundException('Person not found');
}
closestFaceAssetId = person.faceAssetId;
}
const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, {
withHidden,
closestFaceAssetId,
});
const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id);
return {
people: items.map((person) => mapPerson(person)),
hasNextPage,
total,
hidden,
};
}
async reassignFaces(auth: AuthDto, personGroupId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {View on GitHub (pinned to f48d4b3321)