{"record":{"id":"0ca398bd72a2cc2e","repo":"immich-app/immich","slug":"user-is-owner","errorCode":null,"errorMessage":"User is owner","messagePattern":"User is owner","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/album.service.ts","lineNumber":349,"sourceCode":"      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> {\n    await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });\n\n    const album = await this.findOrFail(id, userId, { withAssets: false });\n    const owner = album.albumUsers[0];\n\n    if (owner.user.id === userId) {\n      throw new BadRequestException('User is owner');\n    }\n\n    await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role });\n  }\n\n  private findOrFail(id: string, authUserId: string, options: AlbumInfoOptions) {\n    return findOrFail(() => this.albumRepository.getById(id, options, authUserId), 'Album');\n  }\n}\n","sourceCodeStart":331,"sourceCodeEnd":359,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/album.service.ts#L331-L359","documentation":"Raised by AlbumService.updateUser when attempting to change the role of the album's owner. The endpoint for editing a member's role rejects updates targeting the owner because ownership cannot be changed this way (an album has exactly one owner); the target user must be a non-owner shared member to have their role edited.","triggerScenarios":"PUT /albums/:id/user/:userId with the owner's userId in the path, regardless of the role in the DTO.","commonSituations":"Clients iterating all album members (including the owner) and updating each, or UIs rendering the owner as an editable row.","solutions":["Skip the owner when bulk-updating member roles","Only call updateUser for members with Editor/Viewer roles","Filter the member list client-side: update only entries whose user id differs from album.ownerId"],"exampleFix":"// before\nfor (const m of album.albumUsers) await api.updateAlbumUser(id, m.userId, { role: 'Viewer' });\n// after\nfor (const m of album.albumUsers.filter(m => m.userId !== album.ownerId)) {\n  await api.updateAlbumUser(id, m.userId, { role: 'Viewer' });\n}","handlingStrategy":"validation","validationCode":"const album = await api.getAlbumInfo(albumId);\nif (album.ownerId === userId) throw new Error('Cannot change the owner role');","typeGuard":"function isNonOwnerMember(userId: string, album: { ownerId: string }): boolean {\n  return userId !== album.ownerId;\n}","tryCatchPattern":"try {\n  await api.updateAlbumUser(albumId, userId, { role });\n} catch (e) {\n  if ((e as Error).message === 'User is owner') {\n    return; // skip owner in bulk role updates\n  }\n  throw e;\n}","preventionTips":["Exclude the owner from bulk role-update loops","Render the owner row as read-only in UIs","Only update members whose current role is Editor/Viewer","Compare against album.ownerId before every role change"],"tags":["album","permissions","owner"],"backgroundTag":"unsupported-operation","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"}