fullstackhero/dotnet-starter-kit · warning · ForbiddenException
System groups cannot be modified.
Error message
System groups cannot be modified.
What it means
UpdateGroupCommandHandler throws this ForbiddenException when the target group has IsSystemGroup set. System groups are framework-managed: their name, description, default flag and role assignments form the seed contract the startup syncer depends on, so any modification attempt via the UpdateGroup endpoint is rejected before any validation or persistence happens.
Solutions
- Pick a different, non-system group to modify — fetch the group first and skip or disable editing when IsSystemGroup is true.
- If the system group's role assignments genuinely must change, do it by changing the seed/syncer definition (code-level seed contract), not through the UpdateGroup API.
- Create a new regular group with the desired configuration instead of mutating the system group.
Example fix
// before
await mediator.Send(new UpdateGroupCommand { Id = systemGroupId, Name = "NewName" });
// after
var group = await groupApi.GetByIdAsync(systemGroupId);
if (group.IsSystemGroup)
{
// UI: disable edit button / show read-only view
return;
}
await mediator.Send(new UpdateGroupCommand { Id = systemGroupId, Name = "NewName" }); Defensive patterns
Strategy: validation
Validate before calling
var group = await groupApi.GetByIdAsync(id);
if (group is null) throw new InvalidOperationException("Group not found");
if (group.IsSystemGroup)
throw new InvalidOperationException("System groups cannot be modified via UpdateGroup"); Type guard
bool isEditable(Group g) => g is { IsSystemGroup: false }; Try / catch
try
{
await mediator.Send(new UpdateGroupCommand { Id = id, /* ... */ });
}
catch (ForbiddenException ex)
{
// surface: "This system group is managed by the framework and cannot be edited"
logger.LogWarning(ex, "Attempt to modify system group {GroupId}", id);
} Prevention
- Disable Edit/Delete actions for IsSystemGroup rows in the admin UI.
- Never iterate all group IDs in bulk sync/import scripts — filter out system groups first.
- Change system-group role assignments through the seed definition in code, not the API.
When it happens
Trigger: Sending an UpdateGroup command whose Id resolves to a group where Group.IsSystemGroup == true, regardless of which fields (name, description, IsDefault, RoleIds) the command tries to change.
Common situations: An admin UI lists seeded/system groups alongside normal groups and the user clicks 'Edit' on one; an import/sync script blindly iterates over all group IDs including system ones; a rename of the default 'Administrators'-style group seeded at startup.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Group with ID ' ' not found.
- Group with name ' ' already exists.
- Group with ID ' ' not found.
- System groups cannot be deleted.
- Group with ID ' ' not found.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/35886a526072bd4f.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Groups/UpdateGroup/UpdateGroupCommandHandler.cs:36
public UpdateGroupCommandHandler(IdentityDbContext dbContext, ICurrentUser currentUser, IUserPermissionService userPermissionService)
{
_dbContext = dbContext;
_currentUser = currentUser;
_userPermissionService = userPermissionService;
}
public async ValueTask<GroupDto> Handle(UpdateGroupCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
var group = await GetGroupAsync(command.Id, cancellationToken);
// System groups are framework-managed — name, description, default flag, and role
// assignments are all part of the seed contract that the startup syncer relies on.
if (group.IsSystemGroup)
{
throw new ForbiddenException("System groups cannot be modified.");
}
await ValidateUniqueNameAsync(command.Id, command.Name, cancellationToken);
await ValidateRoleIdsAsync(command.RoleIds, cancellationToken);
var userId = _currentUser.GetUserId().ToString();
group.Update(command.Name, command.Description, userId);
group.SetAsDefault(command.IsDefault, userId);
var currentRoleIdsBefore = group.GroupRoles.Select(gr => gr.RoleId).ToHashSet();
var newRoleIds = UpdateRoleAssignments(group, command.RoleIds);
await _dbContext.SaveChangesAsync(cancellationToken);
// If the set of group→role assignments actually changed, every member's
// effective permission set may have shifted — invalidate each.
if (!currentRoleIdsBefore.SetEquals(newRoleIds))
{
var memberIds = await _dbContext.UserGroupsView on GitHub (pinned to 3f2959e683)