{"record":{"id":"e55fb0da0cf0d830","repo":"immich-app/immich","slug":"invalid-album-thumbnail","errorCode":null,"errorMessage":"Invalid album thumbnail","messagePattern":"Invalid album thumbnail","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/album.service.ts","lineNumber":148,"sourceCode":"      auth.user.id,\n    );\n\n    for (const { userId } of albumUsers) {\n      await this.eventRepository.emit('AlbumInvite', { id: album.id, userId, senderName: auth.user.name });\n    }\n\n    return mapAlbum(album);\n  }\n\n  async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });\n\n    const album = await this.findOrFail(id, auth.user.id, { withAssets: true });\n\n    if (dto.albumThumbnailAssetId) {\n      const results = await this.albumRepository.getAssetIds(id, [dto.albumThumbnailAssetId]);\n      if (results.size === 0) {\n        throw new BadRequestException('Invalid album thumbnail');\n      }\n    }\n    const updatedAlbum = await this.albumRepository.update(\n      album.id,\n      {\n        id: album.id,\n        albumName: dto.albumName,\n        description: dto.description,\n        albumThumbnailAssetId: dto.albumThumbnailAssetId,\n        isActivityEnabled: dto.isActivityEnabled,\n        order: dto.order,\n      },\n      auth.user.id,\n    );\n\n    return mapAlbum({ ...updatedAlbum, assets: album.assets });\n  }\n","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/album.service.ts#L130-L166","documentation":"When updating an album's thumbnail, the service verifies the candidate asset actually belongs to the album via albumRepository.getAssetIds; if the asset is not part of the album, it throws 'Invalid album thumbnail'.","triggerScenarios":"PATCH /albums/:id with albumThumbnailAssetId set to an asset ID that is not in the album (deleted asset, wrong album, or never-added asset).","commonSituations":"Clients caching an old thumbnail asset ID after the asset was removed from the album, or UI state pointing at an asset from a different album.","solutions":["Set albumThumbnailAssetId to an asset ID that currently belongs to the album","Re-fetch the album's assets and pick a valid one from the list","Omit albumThumbnailAssetId from the update DTO to keep the current thumbnail","On the client, handle the 400 by clearing the stale thumbnail reference"],"exampleFix":"// before\nawait api.updateAlbum(id, { albumThumbnailAssetId: staleAssetId });\n// after\nconst album = await api.getAlbumInfo(id);\nconst validId = album.assets.find(a => a.id === staleAssetId)?.id ?? album.albumThumbnailAssetId;\nawait api.updateAlbum(id, { albumThumbnailAssetId: validId });","handlingStrategy":"validation","validationCode":"const assetIds = new Set((await api.getAlbumInfo(albumId)).assets.map(a => a.id));\nif (!assetIds.has(thumbnailAssetId)) throw new Error('thumbnail asset not in album');","typeGuard":"function isInAlbum(assetId: string, album: { assets: { id: string }[] }): boolean {\n  return album.assets.some(a => a.id === assetId);\n}","tryCatchPattern":"try {\n  await api.updateAlbum(id, { albumThumbnailAssetId });\n} catch (e) {\n  if ((e as Error).message === 'Invalid album thumbnail') {\n    return api.updateAlbum(id, {}); // keep current thumbnail\n  }\n  throw e;\n}","preventionTips":["Pick thumbnails only from the album's current asset list","Clear cached thumbnail IDs when assets are removed from the album","Omit albumThumbnailAssetId when unchanged","Re-fetch album info before updates instead of trusting local state"],"tags":["validation","album","thumbnail"],"backgroundTag":"resource-not-found","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}