{"record":{"id":"69bafc86df86d856","repo":"passbolt/passbolt_api","slug":"cannot-update-group-user","errorCode":null,"errorMessage":"Cannot update group user.","messagePattern":"Cannot update group user\\.","errorType":"validation","errorClass":"ValidationException","httpStatus":422,"severity":"error","filePath":"src/Service/GroupsUsers/GroupsUsersUpdateService.php","lineNumber":84,"sourceCode":"     * @throws \\App\\Error\\Exception\\ValidationException Cannot delete the last group manager.\n     */\n    private function assertAtLeastOneGroupManager(GroupsUser $groupUser, array $data): void\n    {\n        $isAdmin = Hash::get($data, 'is_admin');\n        if ($isAdmin || is_null($isAdmin)) {\n            return;\n        }\n        if ($groupUser->is_admin === $isAdmin) {\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 update group user.', $groupUser);\n        }\n    }\n\n    /**\n     * Patch the group user with the data to update.\n     *\n     * @param \\App\\Utility\\UserAccessControl $uac The user at the origin of the operation\n     * @param \\App\\Model\\Entity\\GroupsUser $groupUser The group user to update.\n     * @param array $data The date to use to patch the group user\n     * @return \\App\\Model\\Entity\\GroupsUser\n     */\n    private function patchGroupUserEntity(UserAccessControl $uac, GroupsUser $groupUser, array $data): GroupsUser\n    {\n        $patchEntityOptions = ['accessibleFields' => ['is_admin' => true, 'modified_by' => true]];\n        $groupUser = $this->groupsUsersTable->patchEntity($groupUser, $data, $patchEntityOptions);\n        if ($groupUser->hasErrors()) {\n            $this->handleValidationErrors($groupUser);\n        }","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/GroupsUsers/GroupsUsersUpdateService.php#L66-L102","documentation":"GroupsUsersUpdateService::assertAtLeastOneGroupManager throws this ValidationException when updating a group user (e.g. demoting is_admin to false) would leave the group without any group manager. The detailed message is set on the entity's is_admin field as at_least_one_group_manager.","triggerScenarios":"PUT /groups/users/{id} with data setting is_admin=false on the only group manager of the group (admin count after change would be 0).","commonSituations":"Demoting the sole manager to a normal member, scripts syncing group roles that flip is_admin without checking counts, UI edits in groups with a single manager.","solutions":["Promote another member to is_admin=true first, then demote this user.","Omit is_admin from the update payload if the demotion is unintended.","Verify group manager count via GET /groups/{id} before patching.","If the goal is ownership transfer, do it in one planned sequence: promote, then demote."],"exampleFix":"// before\nawait passbolt.updateGroupUser(soleManagerId, { is_admin: false });\n// after\nawait passbolt.updateGroupUser(otherMemberId, { is_admin: true });\nawait passbolt.updateGroupUser(soleManagerId, { is_admin: false });","handlingStrategy":"validation","validationCode":"const managers = group.memberships.filter(m => m.is_admin);\nif (payload.is_admin === false && managers.length === 1 && managers[0].id === id) return reject('last group manager');","typeGuard":null,"tryCatchPattern":"try { await updateGroupUser(id, payload); } catch (e) { if (e.body?.errors?.is_admin?.at_least_one_group_manager) {...} }","preventionTips":["Guard is_admin=false updates on sole managers","Transfer management before demotion","Verify manager count via GET /groups/{id}"],"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"}