{"record":{"id":"73a8c400848e6ab6","repo":"bitwarden/server","slug":"a-rule-with-that-name-already-exists","errorCode":null,"errorMessage":"A rule with that name already exists.","messagePattern":"A rule with that name already exists\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"bitwarden_license/src/Services/Pam/Services/AccessRuleWriteValidator.cs","lineNumber":58,"sourceCode":"        }\n\n        var conditions = _conditionsValidator.Validate(rule.Conditions);\n        if (!conditions.IsValid)\n        {\n            throw new BadRequestException(conditions.Error!);\n        }\n\n        await ValidateNameIsUniqueAsync(organizationId, rule.Name, existingRuleId);\n\n        return await ValidateCollectionsAsync(organizationId, collectionIds, existingRuleId);\n    }\n\n    private async Task ValidateNameIsUniqueAsync(Guid organizationId, string name, Guid? existingRuleId)\n    {\n        var siblings = await _repository.GetManyByOrganizationIdAsync(organizationId);\n        if (siblings.Any(r => r.Id != existingRuleId && string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)))\n        {\n            throw new BadRequestException(\"A rule with that name already exists.\");\n        }\n    }\n\n    private async Task<List<Guid>> ValidateCollectionsAsync(Guid organizationId, IEnumerable<Guid> collectionIds,\n        Guid? existingRuleId)\n    {\n        var distinctIds = collectionIds.Distinct().ToList();\n        if (distinctIds.Count == 0)\n        {\n            return distinctIds;\n        }\n\n        var collections = await _collectionRepository.GetManyByManyIdsAsync(distinctIds);\n        if (collections.Count != distinctIds.Count)\n        {\n            throw new BadRequestException(\"One or more collections could not be found.\");\n        }\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Services/Pam/Services/AccessRuleWriteValidator.cs#L40-L76","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","solutions":["Use a distinct, organization-unique name for the rule before calling ValidateAsync.","On update, always pass existingRuleId (the rule's own Id) so the validator excludes the rule being edited.","Pre-fetch siblings via GetManyByOrganizationIdAsync and check for collisions client-side before submit.","Normalize names (trim whitespace, enforce casing convention) before submission."],"exampleFix":"// before — update without existingRuleId, own name looks like a duplicate\nawait _validator.ValidateAsync(orgId, rule, collectionIds);\n// after — pass existingRuleId so the rule's own record is excluded\nawait _validator.ValidateAsync(orgId, rule, collectionIds, existingRuleId: rule.Id);","handlingStrategy":"validation","validationCode":"// Pre-check name uniqueness before calling ValidateAsync\nvar siblings = await _repository.GetManyByOrganizationIdAsync(organizationId);\nvar isDuplicate = siblings.Any(r =>\n    r.Id != existingRuleId &&\n    string.Equals(r.Name, proposedName, StringComparison.OrdinalIgnoreCase));\nif (isDuplicate)\n    return BadRequest($\"A rule named '{proposedName}' already exists in this organization.\");","typeGuard":null,"tryCatchPattern":"try { await _validator.ValidateAsync(orgId, rule, collectionIds, existingRuleId); }\ncatch (BadRequestException ex) when (ex.Message.Contains(\"already exists\"))\n{ /* return 409 Conflict or surface to user */ }","preventionTips":["Always pass existingRuleId on updates so the validator excludes the rule's own name.","Enforce a naming convention (e.g., lowercase, no trailing whitespace) to reduce accidental case-variant collisions.","Show existing rule names in the UI before the user submits to avoid duplicates."],"tags":["pam","access-rule","validation","uniqueness","naming"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}