{"record":{"id":"38c6a9e898e97490","repo":"immich-app/immich","slug":"person-not-found","errorCode":null,"errorMessage":"Person not found","messagePattern":"Person not found","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"server/src/services/person.service.ts","lineNumber":69,"sourceCode":"const personKey = ({ ownerId, personGroupId }: PersonId) => `${ownerId}/${personGroupId}`;\n\n@Injectable()\nexport class PersonService extends BaseService {\n  async getAll(auth: AuthDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {\n    const { withHidden = false, closestAssetId, closestPersonId, page, size } = dto;\n    let closestFaceAssetId = closestAssetId;\n    const pagination = {\n      take: size,\n      skip: (page - 1) * size,\n    };\n\n    if (closestPersonId) {\n      const person = await this.personRepository.getByGroupId({\n        ownerId: auth.user.id,\n        personGroupId: closestPersonId,\n      });\n      if (!person?.faceAssetId) {\n        throw new NotFoundException('Person not found');\n      }\n      closestFaceAssetId = person.faceAssetId;\n    }\n    const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, {\n      withHidden,\n      closestFaceAssetId,\n    });\n    const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id);\n\n    return {\n      people: items.map((person) => mapPerson(person)),\n      hasNextPage,\n      total,\n      hidden,\n    };\n  }\n\n  async reassignFaces(auth: AuthDto, personGroupId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/person.service.ts#L51-L87","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nawait api.peopleApi.getPeople({ withHidden: false, closestPersonId: oldPersonId });\n// after\nconst people = await api.peopleApi.getPeople({});\nconst person = people.people.find((p) => p.id === oldPersonId);\nif (person) {\n  await api.peopleApi.getPeople({ withHidden: false, closestPersonId: person.id });\n}","handlingStrategy":"validation","validationCode":"const { people } = await api.peopleApi.getPeople({});\nconst person = people.find((p) => p.id === closestPersonId);\nif (!person || !person.faceAssetId) {\n  throw new Error('Person missing or has no face asset — cannot request closest thumbnail');\n}","typeGuard":"function canRequestClosest(p) {\n  return typeof p?.id === 'string' && typeof p?.faceAssetId === 'string' && p.faceAssetId.length > 0;\n}","tryCatchPattern":"try {\n  await api.peopleApi.getPeople({ closestPersonId, size });\n} catch (e) {\n  if (e.status === 404) {\n    const fresh = await api.peopleApi.getPeople({}); // re-sync IDs after merges/deletions\n  } else throw e;\n}","preventionTips":["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."],"tags":["person","not-found","face","thumbnail"],"backgroundTag":"entity-not-found","analyzedSha":"f48d4b332127ad365ba256108799ca8f571d2dd5","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}