immich-app/immich · error · BadRequestException

Cannot request to join your own cluster group

Error message

Cannot request to join your own cluster group

What it means

Immich's ClusterGroupService.createRequest (POST /cluster-groups/:id/requests, Permission.ClusterGroupRequestCreate) rejects a join request whose userId equals the authenticated user. A cluster group shares face/person clustering between users; requesting to join a group you are already part of is a no-op that would also spam a ClusterGroupRequest event to yourself, so it is blocked with BadRequestException (HTTP 400) before the user lookup and insert run.

Source

Thrown at server/src/services/cluster-group.service.ts:43

    return requests.map((request) => mapClusterGroupRequest(request));
  }

  async getUsers(auth: AuthDto, clusterGroupId: string): Promise<UserResponseDto[]> {
    await this.requireAccess({ auth, permission: Permission.ClusterGroupRead, ids: [clusterGroupId] });

    const users = await this.clusterGroupRepository.getUsers({ clusterGroupId, userId: auth.user.id });
    return users.map((user) => mapUser(user));
  }

  async createRequest(
    auth: AuthDto,
    clusterGroupId: string,
    { userId }: ClusterGroupRequestCreateDto,
  ): Promise<MaybeDuplicate<ClusterGroupRequestResponseDto>> {
    await this.requireAccess({ auth, permission: Permission.ClusterGroupRequestCreate, ids: [clusterGroupId] });

    if (userId === auth.user.id) {
      throw new BadRequestException('Cannot request to join your own cluster group');
    }

    await findOrFail(() => this.userRepository.get(userId, {}), 'User');

    const request = await findOrFail(
      () => this.clusterGroupRepository.createRequest({ clusterGroupId, userId }),
      'Request',
    );

    if (request.isInserted) {
      await this.eventRepository.emit('ClusterGroupRequest', { clusterGroupId, userId, senderName: auth.user.name });
    }

    return { duplicate: !request.isInserted, value: mapClusterGroupRequest(request) };
  }

  async acceptRequest(auth: AuthDto, id: string): Promise<void> {
    await this.requireAccess({ auth, permission: Permission.ClusterGroupRequestRead, ids: [id] });

View on GitHub (pinned to 5666d57f15)

Solutions

  1. Send the id of the user you want to invite (dto.userId of a different user), not auth.user.id.
  2. In the client, exclude the current user from the user picker / candidate list for the invite form.
  3. Add a client-side guard: if (dto.userId === session.user.id) show a validation message instead of hitting the API.

Example fix

// before — UI preselects the logged-in user
await api.post(`/cluster-groups/${groupId}/requests`, {
  userId: session.user.id, // 400: Cannot request to join your own cluster group
});

// after — send the invited member's id and exclude self from pickers
const candidates = allUsers.filter((u) => u.id !== session.user.id);
await api.post(`/cluster-groups/${groupId}/requests`, {
  userId: selectedCandidate.id,
});
Defensive patterns

Strategy: validation

Validate before calling

// Before calling the invite endpoint:
if (dto.userId === auth.user.id) {
  throw new Error('You cannot invite yourself to a cluster group');
}
await createClusterGroupRequest(auth, clusterGroupId, dto); // POST /cluster-groups/{id}/requests

Try / catch

try {
  return await api.post(`/cluster-groups/${groupId}/requests`, { userId });
} catch (error) {
  if (isHttpError(error, 400, 'Cannot request to join your own cluster group')) {
    // refresh the candidate list, exclude the session user, and reprompt
    return showInviteForm(candidates.filter((u) => u.id !== session.user.id));
  }
  throw error;
}

Prevention

When it happens

Trigger: POST /cluster-groups/{clusterGroupId}/requests with body { "userId": "<your own user id>" } — i.e. the sender of the request and the invited user are the same person. Typically caused by a UI defaulting the user picker to the current user, or a script iterating a member list that accidentally includes the caller.

Common situations: Frontend invite forms that preselect the logged-in user. Test harnesses that seed one user and call the invite endpoint with that same user. Copy-pasted invitation code where auth.user.id is passed instead of the selected member's id.

Related errors


AI-assisted analysis of immich-app/immich@5666d57f15 (2026-08-21). Data as JSON: /api/errors/0e6400fad9466ada. Report an issue: GitHub.