{"record":{"id":"778d07369b62e64e","repo":"immich-app/immich","slug":"album-not-shared-with-user","errorCode":null,"errorMessage":"Album not shared with user","messagePattern":"Album not shared with user","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/album.service.ts","lineNumber":324,"sourceCode":"      }\n\n      await this.albumUserRepository.create({ userId, albumId: id, role });\n      await this.eventRepository.emit('AlbumInvite', { id, userId, senderName: auth.user.name });\n    }\n\n    return mapAlbum(await this.findOrFail(id, auth.user.id, { withAssets: true }));\n  }\n\n  async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {\n    if (userId === 'me') {\n      userId = auth.user.id;\n    }\n\n    const album = await this.findOrFail(id, auth.user.id, { withAssets: false });\n\n    const exists = album.albumUsers.find(({ user: { id } }) => id === userId);\n    if (!exists) {\n      throw new BadRequestException('Album not shared with user');\n    }\n\n    if (\n      exists.role === AlbumUserRole.Owner &&\n      album.albumUsers.filter(({ role }) => role === AlbumUserRole.Owner).length === 1\n    ) {\n      throw new BadRequestException('Cannot remove the last album owner');\n    }\n\n    // non-admin can remove themselves\n    if (auth.user.id !== userId) {\n      await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });\n    }\n\n    await this.albumUserRepository.delete({ albumId: id, userId });\n  }\n\n  async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/album.service.ts#L306-L342","documentation":"Raised by AlbumService.removeUser when the target userId (after 'me' is resolved to auth.user.id) does not appear in album.albumUsers. The guard looks up the user in the album's shared-user list and fails if they were never invited or already removed, so the deletion of the album user row never happens. Indicates the client is trying to unshare from a user who has no membership in this album.","triggerScenarios":"DELETE /albums/:id/user/:userId where userId was never added to the album or was already removed (double-delete, stale UI state).","commonSituations":"Clicking remove twice, two admins removing the same member concurrently, or a client cache showing a member who already left/was removed.","solutions":["Re-fetch the album to confirm the user is still a member before removing","Treat the 400 as already-removed and refresh local album state","Remove a different, existing member ID (check for ID typos)"],"exampleFix":"// before\nawait api.removeAlbumUser(albumId, userId);\n// after\nconst album = await api.getAlbumInfo(albumId);\nif (album.albumUsers.some(u => u.userId === userId)) {\n  await api.removeAlbumUser(albumId, userId);\n}","handlingStrategy":"validation","validationCode":"const album = await api.getAlbumInfo(albumId);\nif (!album.albumUsers.some(m => m.userId === userId)) {\n  throw new Error(`User ${userId} is not a member of album ${albumId}`);\n}","typeGuard":"function isMember(userId: string, album: { albumUsers: { user: { id: string } }[] }): boolean {\n  return album.albumUsers.some(m => m.user.id === userId);\n}","tryCatchPattern":"try {\n  await api.removeAlbumUser(albumId, userId);\n} catch (e) {\n  if ((e as Error).message === 'Album not shared with user') {\n    return; // already removed — idempotent no-op\n  }\n  throw e;\n}","preventionTips":["Make removal idempotent: treat not-shared as success","Refresh album state after any membership change","Disable remove buttons for non-members in the UI","Avoid double-submitting remove requests"],"tags":["album","user","state"],"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"}