bitwarden/server · error · BadRequestException

A rule with that name already exists.

Error message

A rule with that name already exists.

What it means

Thrown by AccessRuleWriteValidator.ValidateNameIsUniqueAsync (line 58) when creating or updating a PAM access rule whose Name case-insensitively matches another rule in the same organization. The check loads every sibling via _repository.GetManyByOrganizationIdAsync, then compares with StringComparison.OrdinalIgnoreCase, excluding the rule being updated by existingRuleId (null for creates means every sibling is compared).

Source

Thrown at bitwarden_license/src/Services/Pam/Services/AccessRuleWriteValidator.cs:58

        }

        var conditions = _conditionsValidator.Validate(rule.Conditions);
        if (!conditions.IsValid)
        {
            throw new BadRequestException(conditions.Error!);
        }

        await ValidateNameIsUniqueAsync(organizationId, rule.Name, existingRuleId);

        return await ValidateCollectionsAsync(organizationId, collectionIds, existingRuleId);
    }

    private async Task ValidateNameIsUniqueAsync(Guid organizationId, string name, Guid? existingRuleId)
    {
        var siblings = await _repository.GetManyByOrganizationIdAsync(organizationId);
        if (siblings.Any(r => r.Id != existingRuleId && string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)))
        {
            throw new BadRequestException("A rule with that name already exists.");
        }
    }

    private async Task<List<Guid>> ValidateCollectionsAsync(Guid organizationId, IEnumerable<Guid> collectionIds,
        Guid? existingRuleId)
    {
        var distinctIds = collectionIds.Distinct().ToList();
        if (distinctIds.Count == 0)
        {
            return distinctIds;
        }

        var collections = await _collectionRepository.GetManyByManyIdsAsync(distinctIds);
        if (collections.Count != distinctIds.Count)
        {
            throw new BadRequestException("One or more collections could not be found.");
        }

View on GitHub (pinned to e93b962371)

Solutions

  1. Use a distinct, organization-unique name for the rule before calling ValidateAsync.
  2. On update, always pass existingRuleId (the rule's own Id) so the validator excludes the rule being edited.
  3. Pre-fetch siblings via GetManyByOrganizationIdAsync and check for collisions client-side before submit.
  4. Normalize names (trim whitespace, enforce casing convention) before submission.

Example fix

// before — update without existingRuleId, own name looks like a duplicate
await _validator.ValidateAsync(orgId, rule, collectionIds);
// after — pass existingRuleId so the rule's own record is excluded
await _validator.ValidateAsync(orgId, rule, collectionIds, existingRuleId: rule.Id);
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check name uniqueness before calling ValidateAsync
var siblings = await _repository.GetManyByOrganizationIdAsync(organizationId);
var isDuplicate = siblings.Any(r =>
    r.Id != existingRuleId &&
    string.Equals(r.Name, proposedName, StringComparison.OrdinalIgnoreCase));
if (isDuplicate)
    return BadRequest($"A rule named '{proposedName}' already exists in this organization.");

Try / catch

try { await _validator.ValidateAsync(orgId, rule, collectionIds, existingRuleId); }
catch (BadRequestException ex) when (ex.Message.Contains("already exists"))
{ /* return 409 Conflict or surface to user */ }

Prevention

When it happens

Trigger: Calling ValidateAsync with an AccessRule whose Name matches a sibling's Name (case-insensitive) where the sibling's Id != existingRuleId. Happens on both create (existingRuleId null) and update (existingRuleId omitted or wrong).

Common situations: Admin creating a rule named 'Prod' when 'prod' already exists; renaming during update and forgetting to pass existingRuleId so the validator sees the rule's own name as a collision; concurrent rule creation by two admins; test fixtures not cleaning up names.

Related errors


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