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
- Send the id of the user you want to invite (dto.userId of a different user), not auth.user.id.
- In the client, exclude the current user from the user picker / candidate list for the invite form.
- 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
- Exclude the current user from cluster-group invite pickers and candidate lists.
- In scripts, assert dto.userId !== auth.user.id before sending the request.
- Remember the server also 404s on unknown userIds (findOrFail on userRepository.get), so validate the target user exists too.
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
- Cannot leave a cluster group without any other members
- Sidecar files cannot be deleted
- Cannot rotate an API Key with permissions you do not have
- The ${name} extension is not available in this Postgres inst
- The ${name} extension version is ${version}, which mean
AI-assisted analysis of immich-app/immich@5666d57f15 (2026-08-21).
Data as JSON: /api/errors/0e6400fad9466ada.
Report an issue: GitHub.