{"record":{"id":"f0bba5e41e283a3f","repo":"passbolt/passbolt_api","slug":"cannot-delete-group-user","errorCode":null,"errorMessage":"Cannot delete group user.","messagePattern":"Cannot delete group user\\.","errorType":"validation","errorClass":"ValidationException","httpStatus":422,"severity":"error","filePath":"src/Service/GroupsUsers/GroupsUsersDeleteService.php","lineNumber":114,"sourceCode":"     * Assert that the group to remove the group user in will have at least one manager after removing the group user.\n     *\n     * @param \\App\\Model\\Entity\\GroupsUser $groupUser The group user to check the group for\n     * @return void\n     * @throws \\App\\Error\\Exception\\ValidationException Cannot delete the last group manager.\n     */\n    private function assertAtLeastOneGroupManager(GroupsUser $groupUser): void\n    {\n        if (!$groupUser->is_admin) {\n            return;\n        }\n\n        $groupManagersCount = $this->groupsUsersTable->findByGroupIdAndIsAdmin($groupUser->group_id, true)\n            ->all()\n            ->count();\n\n        if ($groupManagersCount === 1) {\n            $groupUser->setError('is_admin', ['at_least_one_group_manager' => 'Cannot delete the last group manager.']);\n            throw new ValidationException('Cannot delete group user.', $groupUser);\n        }\n    }\n\n    /**\n     * Delete the secrets for the resources the user lost access after being removed from the group.\n     *\n     * @param \\App\\Model\\Entity\\GroupsUser $groupUser The group user to delete.\n     * @return array<\\App\\Model\\Entity\\Secret>\n     */\n    private function deleteLostAccessAssociatedSecrets(GroupsUser $groupUser): array\n    {\n        $lostAccessSecretsConditions = [\n            'user_id' => $groupUser->user_id,\n            'resource_id IN' => $this->findLostAccessResourcesIdsQuery($groupUser),\n        ];\n        /** @var array<\\App\\Model\\Entity\\Secret> $lostAccessSecrets */\n        $lostAccessSecrets = $this->secretsTable->find()\n            ->select(['id', 'resource_id', 'user_id'])","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/GroupsUsers/GroupsUsersDeleteService.php#L96-L132","documentation":"GroupsUsersDeleteService::assertAtLeastOneGroupManager throws this ValidationException when deleting a group user would leave the group with zero group managers (i.e. the deleted user is the only is_admin member). The real reason is attached as a validation error on the entity's is_admin field.","triggerScenarios":"DELETE /groups/users/{id} where the target group user is the sole admin of the group ($groupManagersCount === 1 and the user is that admin).","commonSituations":"Removing the last group manager before another member is promoted, bulk-cleanups of group memberships, deleting memberships via API without checking is_admin distribution.","solutions":["Promote another group member to admin (PUT /groups/users/{id} with is_admin=true) before deleting this one.","Delete a different member first if another manager exists.","Check GET /groups/{id} memberships for other is_admin users before calling delete.","If intending to dissolve the group, delete the whole group instead (DELETE /groups/{id})."],"exampleFix":"// before\nawait passbolt.deleteGroupUser(lastManagerId);\n// after\nawait passbolt.updateGroupUser(otherMemberId, { is_admin: true });\nawait passbolt.deleteGroupUser(lastManagerId);","handlingStrategy":"validation","validationCode":"const managers = group.memberships.filter(m => m.is_admin);\nif (managers.length === 1 && managers[0].id === targetId) throw new Error('last group manager');","typeGuard":null,"tryCatchPattern":"try { await deleteGroupUser(id); } catch (e) { if (e.body?.errors?.is_admin?.at_least_one_group_manager) {...} }","preventionTips":["Always check is_admin distribution before deleting memberships","Promote a backup manager for large groups"],"tags":["groups","permissions","validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}