immich-app/immich · error · BadRequestException
Cannot remove the last album owner
Error message
Cannot remove the last album owner
What it means
Raised by AlbumService.removeUser when the member being removed is an Owner and is the only owner in the album (albumUsers filtered to role Owner has length 1). This prevents deleting the last owner and leaving the album ownerless; ownership must first be transferred to another member via updateUser before removal.
Solutions
- The owner must transfer or delete the album instead of removing themselves
- Promote/keep another owner first — though note addUsers cannot create owners, so owner removal is generally impossible by design
- Remove non-owner members instead; the owner is removed only with album deletion
Example fix
// before
await api.removeAlbumUser(albumId, ownerId); // 400 if last owner
// after
if (album.ownerId !== userId) {
await api.removeAlbumUser(albumId, userId);
} else {
await api.deleteAlbum(albumId); // owner leaves by deleting
} Defensive patterns
Strategy: validation
Validate before calling
const album = await api.getAlbumInfo(albumId);
const target = album.albumUsers.find(m => m.userId === userId);
if (target?.role === 'Owner') throw new Error('Cannot remove the last/only album owner'); Type guard
function isRemovableMember(userId: string, album: { albumUsers: { user: { id: string }; role: string }[] }): boolean {
const m = album.albumUsers.find(m => m.user.id === userId);
return !!m && m.role !== 'Owner';
} Try / catch
try {
await api.removeAlbumUser(albumId, userId);
} catch (e) {
if ((e as Error).message === 'Cannot remove the last album owner') {
throw new Error('Delete the album or transfer ownership instead');
}
throw e;
} Prevention
- Never offer a remove action for the owner row in UIs
- Remove only Editor/Viewer members
- Use album deletion for the owner to leave
- Check the member's role before calling removeUser
When it happens
Trigger: DELETE /albums/:id/user/:userId where the target is the sole Owner of the album (typically the album creator).
Common situations: Owners trying to remove themselves while still the only owner, or admins attempting to clean up members starting with the owner entry.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot add another owner
- User is owner
- Album must have an owner
- Album not shared with user
- Failed to create
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/749c64292ca52961.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/album.service.ts:331
}
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
if (userId === 'me') {
userId = auth.user.id;
}
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
if (!exists) {
throw new BadRequestException('Album not shared with user');
}
if (
exists.role === AlbumUserRole.Owner &&
album.albumUsers.filter(({ role }) => role === AlbumUserRole.Owner).length === 1
) {
throw new BadRequestException('Cannot remove the last album owner');
}
// non-admin can remove themselves
if (auth.user.id !== userId) {
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
}
await this.albumUserRepository.delete({ albumId: id, userId });
}
async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
const album = await this.findOrFail(id, userId, { withAssets: false });
const owner = album.albumUsers[0];
if (owner.user.id === userId) {
throw new BadRequestException('User is owner');View on GitHub (pinned to e55ac299a4)