fullstackhero/dotnet-starter-kit · error · CustomException
operation failed
Error message
operation failed
What it means
RemoveRevokedPermissionsAsync calls roleManager.RemoveClaimAsync per revoked claim and throws CustomException("operation failed") with the Identity error descriptions when the removal does not succeed (result.Succeeded == false).
Solutions
- Retry the whole UpdatePermissionsAsync call after re-reading the role
- Serialize permission updates per role (optimistic concurrency or lock)
- Inspect result.Errors in the CustomException data for the underlying Identity reason
Example fix
// before
await roleService.UpdatePermissionsAsync(roleId, perms, ct);
// after
try { await roleService.UpdatePermissionsAsync(roleId, perms, ct); }
catch (CustomException) { var fresh = await GetFreshPermsAsync(roleId, ct); await roleService.UpdatePermissionsAsync(roleId, Merge(fresh, perms), ct); } Defensive patterns
Strategy: retry
Validate before calling
var current = (await roleService.GetWithPermissionsAsync(roleId, ct)).Permissions; var toRemove = current.Except(desired).ToList(); // log this; empty list => no RemoveClaimAsync path
Try / catch
try { await roleService.UpdatePermissionsAsync(roleId, desired, ct); }
catch (CustomException ex) {
logger.LogWarning(ex, "Permission update failed for {RoleId}: {@Errors}", roleId, ex.Data["errors"]);
await Task.Delay(200, ct); // then retry once with fresh state
} Prevention
- Avoid two admins editing the same role's permissions concurrently
- Reload permissions before each save instead of merging a stale list
- Read the Identity error descriptions carried in the exception for root cause
When it happens
Trigger: The role store rejects the claim removal — e.g. the role was deleted concurrently, concurrency stamp changed (row updated elsewhere), or the database is unavailable.
Common situations: Two admins saving the same role's permissions simultaneously (concurrency stamp mismatch); DB connection drop mid-update; claim row already removed by another request.
Related errors
- Roles not found
- Roles not found
- Role store not configured. Ensure .AddRoles
- role not found
- System role permissions are managed by the framework and…
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/2b25d56e5620c773.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Roles/RoleService.cs:217
// Strip every permission flagged IsRoot in the registry. (A prior prefix check on "Permissions.Root."
// was a no-op — no root perm uses that prefix — letting a tenant admin grant themselves root perms.)
var rootOnly = PermissionConstants.Root.Select(p => p.Name).ToHashSet(StringComparer.Ordinal);
permissions.RemoveAll(rootOnly.Contains);
}
private async Task RemoveRevokedPermissionsAsync(FshRole role, IList<System.Security.Claims.Claim> currentClaims, List<string> permissions, CancellationToken cancellationToken = default)
{
var claimsToRemove = currentClaims.Where(c => !permissions.Exists(p => p == c.Value));
foreach (var claim in claimsToRemove)
{
cancellationToken.ThrowIfCancellationRequested();
var result = await roleManager.RemoveClaimAsync(role, claim);
if (!result.Succeeded)
{
var errors = result.Errors.Select(error => error.Description).ToList();
throw new CustomException("operation failed", errors);
}
}
}
private async Task AddNewPermissionsAsync(FshRole role, IList<System.Security.Claims.Claim> currentClaims, List<string> permissions, CancellationToken cancellationToken = default)
{
var newPermissions = permissions
.Where(p => !string.IsNullOrEmpty(p) && !currentClaims.Any(c => c.Value == p))
.ToList();
foreach (string permission in newPermissions)
{
context.RoleClaims.Add(new FshRoleClaim
{
RoleId = role.Id,
ClaimType = ClaimConstants.Permission,
ClaimValue = permission,
CreatedBy = currentUser.GetUserId().ToString()View on GitHub (pinned to 3f2959e683)