{"record":{"id":"a94ec29d53dcf351","repo":"bitwarden/server","slug":"externalid-already-exists-for-another-group","errorCode":null,"errorMessage":"ExternalId already exists for another group.","messagePattern":"ExternalId already exists for another group\\.","errorType":"exception","errorClass":"ConflictException","httpStatus":409,"severity":"error","filePath":"bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs","lineNumber":205,"sourceCode":"        }\n\n        await EnsureExternalIdIsValidAsync(group, newExternalId);\n        return newExternalId;\n    }\n\n    private async Task EnsureExternalIdIsValidAsync(Group group, string newExternalId)\n    {\n        if (newExternalId.Length > 300)\n        {\n            throw new BadRequestException(\"ExternalId cannot exceed 300 characters.\");\n        }\n\n        var existingGroups = await _groupRepository.GetManyByOrganizationIdAsync(group.OrganizationId);\n        if (existingGroups.Any(g => g.Id != group.Id &&\n                                    !string.IsNullOrWhiteSpace(g.ExternalId) &&\n                                    g.ExternalId.Equals(newExternalId, StringComparison.OrdinalIgnoreCase)))\n        {\n            throw new ConflictException(\"ExternalId already exists for another group.\");\n        }\n    }\n\n    private async Task AddMembersAsync(Group group, HashSet<Guid> usersToAdd)\n    {\n        // Azure Entra ID is known to send redundant \"add\" requests for each existing member every time any member\n        // is removed. To avoid excessive load on the database, we check against the high availability replica and\n        // return early if they already exist.\n        var groupMembers = await _groupRepository.GetManyUserIdsByIdAsync(group.Id, useReadOnlyReplica: true);\n        if (usersToAdd.IsSubsetOf(groupMembers))\n        {\n            _logger.LogDebug(\"Ignoring duplicate SCIM request to add members {Members} to group {Group}\", usersToAdd, group.Id);\n            return;\n        }\n\n        await _groupRepository.AddGroupUsersByIdAsync(group.Id, usersToAdd, _timeProvider.GetUtcNow().UtcDateTime);\n    }\n","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs#L187-L223","documentation":"Thrown as a ConflictException (HTTP 409) by PatchGroupCommand.EnsureExternalIdIsValidAsync when another group in the same organization already claims the same externalId (case-insensitive comparison). ExternalId must be unique within an organization so the IdP can reliably address exactly one group.","triggerScenarios":"SCIM PATCH /v2/{organizationId}/Groups/{id} with a 'replace' on 'externalId' where the new value matches an existing group's externalId in the same org (excluding the group being patched itself). Happens when two directory groups share an identifier or after a rename/re-import.","commonSituations":"IdP re-exported groups with overlapping externalIds after a directory restructure. An admin manually duplicated a group's externalId. Case-variant collisions (e.g., 'GroupA' vs 'groupa') from IdPs that treat externalId as case-insensitive.","solutions":["Find the conflicting group: list groups via GET /v2/{organizationId}/Users and search for the duplicate externalId.","Assign a unique externalId to the group being patched, or remove the externalId from the conflicting group first.","Fix the IdP attribute mapping so each group gets a distinct, stable externalId.","If the externalId belongs to a deleted group, verify it was fully removed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Check for duplicate externalId before patching\nvar groups = await scimClient.ListGroupsAsync(orgId);\nvar dup = groups.FirstOrDefault(g => g.ExternalId?.Equals(newExternalId, StringComparison.OrdinalIgnoreCase) == true && g.Id != groupId);\nif (dup != null) throw new InvalidOperationException($\"externalId already used by group {dup.Id}\");","typeGuard":null,"tryCatchPattern":"try { await scimClient.PatchGroupExternalIdAsync(orgId, groupId, newExternalId); }\ncatch (ScimException ex) when (ex.StatusCode == 409)\n{ /* resolve the duplicate: change the externalId or clear it from the other group */ }","preventionTips":["Ensure externalIds are unique across all groups in the source directory.","Run a deduplication check before syncing.","Treat 409 on externalId as a data conflict requiring manual resolution, not a retry."],"tags":["scim","conflict","groups","http-409","externalid","uniqueness","patch"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}