{"record":{"id":"ad00eefa119fb651","repo":"immich-app/immich","slug":"invalid-assetid-for-feature-face-or-asset-is-offli","errorCode":null,"errorMessage":"Invalid assetId for feature face or asset is offline","messagePattern":"Invalid assetId for feature face or asset is offline","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/person.service.ts","lineNumber":217,"sourceCode":"      isFavorite: dto.isFavorite,\n      color: dto.color,\n    });\n\n    return mapPerson(person);\n  }\n\n  async update(auth: AuthDto, personGroupId: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [personGroupId] });\n\n    const { ownerId } = await this.findOrFail(auth, personGroupId);\n    const { name, birthDate, isHidden, featureFaceAssetId: assetId, isFavorite, color } = dto;\n    // TODO: set by faceId directly\n    let faceId: string | undefined;\n    if (assetId) {\n      await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [assetId] });\n      const face = await this.personRepository.getForFeatureFaceUpdate({ personGroupId, assetId });\n      if (!face) {\n        throw new BadRequestException('Invalid assetId for feature face or asset is offline');\n      }\n\n      faceId = face.id;\n    }\n\n    const person = await this.personRepository.update({\n      ownerId,\n      personGroupId,\n      faceAssetId: faceId,\n      name,\n      birthDate,\n      isHidden,\n      isFavorite,\n      color,\n    });\n\n    if (assetId) {\n      await this.jobRepository.queue({ name: JobName.PersonGenerateThumbnail, data: { ownerId, personGroupId } });","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/person.service.ts#L199-L235","documentation":"PersonService.update, when setting a feature face by assetId, first checks read access and then queries getForFeatureFaceUpdate to resolve a face for that (personGroupId, assetId) pair. If no face row is found — the asset has no recognized face for this person, or the asset is offline/not yet indexed — it throws BadRequestException with this message.","triggerScenarios":"PUT /api/people/{id} with an assetId whose asset has no face linked to that person, the asset is offline (not on disk/in library), or the assetId belongs to a different person's faces.","commonSituations":"Client picked an asset from an old preview before re-indexing, asset file moved/deleted (offline storage), choosing an asset not containing the person, or IDs mixed up after a merge.","solutions":["Choose an assetId that actually contains a detected face for this person (list the person's assets first).","Restore the offline asset to its library path and let the server re-index so the face row exists.","Re-run face detection/recognition if the asset was added but faces were not yet processed.","Confirm the personId matches the person shown in the client to avoid cross-person asset selection."],"exampleFix":"// before\nawait api.peopleApi.updatePerson({ id: personId, personUpdateDto: { featureFaceAssetId: anyAssetId } });\n// after\nconst assets = await api.peopleApi.getPersonAssets({ id: personId });\nif (assets.length > 0) {\n  await api.peopleApi.updatePerson({ id: personId, personUpdateDto: { featureFaceAssetId: assets[0].id } });\n}","handlingStrategy":"validation","validationCode":"const assets = await api.peopleApi.getPersonAssets({ id: personId });\nconst valid = assets.some((a) => a.id === assetId);\nif (!valid) throw new Error('assetId has no face for this person or asset is offline');","typeGuard":null,"tryCatchPattern":"try {\n  await api.peopleApi.updatePerson({ id: personId, personUpdateDto: { featureFaceAssetId: assetId } });\n} catch (e) {\n  if (e.status === 400 && String(e.message).includes('Invalid assetId for feature face')) {\n    // pick an asset from getPersonAssets or restore offline asset\n  } else throw e;\n}","preventionTips":["Only choose feature faces from assets returned for that person.","Ensure all library files are online before re-indexing faces.","Keep face detection/recognition jobs completed before editing people."],"tags":["person","face","asset","bad-request","validation"],"backgroundTag":"invalid-argument-value","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"}