{"record":{"id":"fbeed7719c1b7764","repo":"immich-app/immich","slug":"not-found","errorCode":null,"errorMessage":"Not Found","messagePattern":"Not Found","errorType":"http","errorClass":"NotFoundException","httpStatus":404,"severity":"warning","filePath":"server/src/services/person.service.ts","lineNumber":166,"sourceCode":"\n    await this.jobRepository.queueAll(jobs);\n  }\n\n  async getById(auth: AuthDto, id: string): Promise<PersonResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] });\n    return mapPerson(await this.findOrFail(id));\n  }\n\n  async getStatistics(auth: AuthDto, id: string): Promise<PersonStatisticsResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] });\n    return this.personRepository.getStatistics(id);\n  }\n\n  async getThumbnail(auth: AuthDto, id: string): Promise<ImmichFileResponse> {\n    await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] });\n    const person = await this.personRepository.getById(id);\n    if (!person || !person.thumbnailPath) {\n      throw new NotFoundException();\n    }\n\n    return new ImmichFileResponse({\n      path: person.thumbnailPath,\n      contentType: mimeTypes.lookup(person.thumbnailPath),\n      cacheControl: CacheControl.PrivateWithoutCache,\n    });\n  }\n\n  async create(auth: AuthDto, dto: PersonCreateDto): Promise<PersonResponseDto> {\n    const person = await this.personRepository.create({\n      ownerId: auth.user.id,\n      name: dto.name,\n      birthDate: dto.birthDate,\n      isHidden: dto.isHidden,\n      isFavorite: dto.isFavorite,\n      color: dto.color,\n    });","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/person.service.ts#L148-L184","documentation":"Thrown by PersonService.getThumbnail when personRepository.getById(id) returns null or the person has no thumbnailPath. NotFoundException with no message -> HTTP 404 with body 'Not Found'. Used after the permission check passes, so it indicates the thumbnail file is missing even though the person is accessible.","triggerScenarios":"GET /people/{id}/thumbnail for a person whose thumbnail was never generated, was deleted from disk, or whose thumbnailPath column is empty.","commonSituations":"Thumbnail generation job failed or was interrupted; storage path was migrated/cleaned; the person was created manually (no face) and never had a thumbnail; cold migration left files behind.","solutions":["Re-run the thumbnail generation queue for faces (Administration > Jobs > Thumbnail Generation).","Verify the storage volume is mounted and the thumbnail path on disk is readable.","Provide a message to the NotFoundException so clients can distinguish 'no thumbnail' from 'no person'."],"exampleFix":"// before\nconst person = await this.personRepository.getById(id);\nif (!person || !person.thumbnailPath) {\n  throw new NotFoundException();\n}\n\n// after\nconst person = await this.personRepository.getById(id);\nif (!person) {\n  throw new NotFoundException(`Person ${id} not found`);\n}\nif (!person.thumbnailPath) {\n  throw new NotFoundException(`Person ${id} has no thumbnail`);\n}","handlingStrategy":"validation","validationCode":"// Before requesting the thumbnail, verify the person has one.\nconst person = await personService.getById(auth, id);\nif (!person || !person.hasThumbnail) {\n  // render a placeholder avatar instead of calling the thumbnail endpoint\n  return PLACEHOLDER_AVATAR;\n}","typeGuard":"const hasThumbnail = (p: PersonResponseDto | null | undefined): boolean =>\n  !!p && (!!p.thumbnailPath || !!p.hasThumbnail);","tryCatchPattern":"try {\n  return await personService.getThumbnail(auth, id);\n} catch (e) {\n  if (e instanceof NotFoundException) {\n    // 404 from this endpoint means no thumbnail file; fall back to placeholder\n    return PLACEHOLDER_AVATAR;\n  }\n  throw e;\n}","preventionTips":["Run the Thumbnail Generation job after facial recognition changes.","Verify storage mounts are present after migrations.","Add a message to the NotFoundException to distinguish missing-person from missing-thumbnail."],"tags":["person","thumbnail","storage","not-found","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}