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

  1. Pick a different, non-system group to modify — fetch the group first and skip or disable editing when IsSystemGroup is true.
  2. 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.
  3. 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

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


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.UserGroups

View on GitHub (pinned to 3f2959e683)