immich-app/immich · error · BadRequestException

Album not shared with user

Error message

Album not shared with user

What it means

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.

Solutions

  1. Re-fetch the album to confirm the user is still a member before removing
  2. Treat the 400 as already-removed and refresh local album state
  3. Remove a different, existing member ID (check for ID typos)

Example fix

// before
await api.removeAlbumUser(albumId, userId);
// after
const album = await api.getAlbumInfo(albumId);
if (album.albumUsers.some(u => u.userId === userId)) {
  await api.removeAlbumUser(albumId, userId);
}
Defensive patterns

Strategy: validation

Validate before calling

const album = await api.getAlbumInfo(albumId);
if (!album.albumUsers.some(m => m.userId === userId)) {
  throw new Error(`User ${userId} is not a member of album ${albumId}`);
}

Type guard

function isMember(userId: string, album: { albumUsers: { user: { id: string } }[] }): boolean {
  return album.albumUsers.some(m => m.user.id === userId);
}

Try / catch

try {
  await api.removeAlbumUser(albumId, userId);
} catch (e) {
  if ((e as Error).message === 'Album not shared with user') {
    return; // already removed — idempotent no-op
  }
  throw e;
}

Prevention

When it happens

Trigger: DELETE /albums/:id/user/:userId where userId was never added to the album or was already removed (double-delete, stale UI state).

Common situations: Clicking remove twice, two admins removing the same member concurrently, or a client cache showing a member who already left/was removed.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/778d07369b62e64e. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/album.service.ts:324

      }

      await this.albumUserRepository.create({ userId, albumId: id, role });
      await this.eventRepository.emit('AlbumInvite', { id, userId, senderName: auth.user.name });
    }

    return mapAlbum(await this.findOrFail(id, auth.user.id, { withAssets: true }));
  }

  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> {

View on GitHub (pinned to e55ac299a4)