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

  1. Refresh the people list (GET /api/people) and use a current person ID for closestPersonId.
  2. Assign a face to the person (pick a new feature face) if the person exists but has no faceAssetId.
  3. Verify the person belongs to the authenticated user; person IDs are per-owner.
  4. 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

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


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)