bitwarden/server · error · ConflictException

Conflict.

Error message

Conflict.

What it means

Thrown as a ConflictException (HTTP 409) with the default message 'Conflict.' by PostGroupCommand.PostGroupAsync when creating a group whose externalId already matches an existing group in the same organization. Unlike the patch path, this uses exact string comparison (==) rather than case-insensitive. The default ConflictException constructor supplies the message 'Conflict.'.

Source

Thrown at bitwarden_license/src/Scim/Groups/PostGroupCommand.cs:34

    public PostGroupCommand(
        IGroupRepository groupRepository,
        ICreateGroupCommand createGroupCommand)
    {
        _groupRepository = groupRepository;
        _createGroupCommand = createGroupCommand;
    }

    public async Task<Group> PostGroupAsync(Organization organization, ScimGroupRequestModel model)
    {
        if (string.IsNullOrWhiteSpace(model.DisplayName))
        {
            throw new BadRequestException();
        }

        var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
        if (!string.IsNullOrWhiteSpace(model.ExternalId) && groups.Any(g => g.ExternalId == model.ExternalId))
        {
            throw new ConflictException();
        }

        var group = model.ToGroup(organization.Id);
        await _createGroupCommand.CreateGroupAsync(group, organization, EventSystemUser.SCIM, collections: null);
        await UpdateGroupMembersAsync(group, model);

        return group;
    }

    private async Task UpdateGroupMembersAsync(Group group, ScimGroupRequestModel model)
    {
        if (model.Members == null)
        {
            return;
        }

        var memberIds = new List<Guid>();
        foreach (var id in model.Members.Select(i => i.Value))

View on GitHub (pinned to e93b962371)

Solutions

  1. Check if the group already exists: GET /v2/{organizationId}/Groups?filter=externalId eq "...".
  2. If it exists, use PUT or PATCH to update it instead of POSTing a new one.
  3. Deduplicate externalIds in the source directory before provisioning.
  4. Configure the IdP to treat 409 as 'already exists' and reconcile rather than retry-create.
Defensive patterns

Strategy: validation

Validate before calling

// Before creating, check if a group with this externalId exists
var existing = await scimClient.ListGroupsAsync(orgId, filter: $"externalId eq \"{externalId}\"");
if (existing.Any()) { /* use PUT/PATCH on existing instead */ }

Try / catch

try { await scimClient.CreateGroupAsync(orgId, model); }
catch (ScimException ex) when (ex.StatusCode == 409)
{ // group likely exists already — fetch and update instead
  var existing = await scimClient.ListGroupsAsync(orgId, filter: $"externalId eq \"{model.ExternalId}\"");
  if (existing.Any()) await scimClient.PutGroupAsync(orgId, existing.First().Id, model); }

Prevention

When it happens

Trigger: POST /v2/{organizationId}/Groups with a non-empty externalId that matches an existing group's externalId in the same org. Common when the IdP retries a creation that partially succeeded, or when two groups in the directory share the same externalId.

Common situations: IdP re-provisioning after a timeout where the first POST succeeded but the response was lost. Duplicate externalIds in the source directory. Directory connector re-sync without deduplication.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/cfa97dc4ab0bba06. Report an issue: GitHub.