fullstackhero/dotnet-starter-kit · error · NotFoundException

User ' ' is not a member of group ' '.

Error message

User '{command.UserId}' is not a member of group '{command.GroupId}'.

What it means

RemoveUserFromGroup looks up the UserGroups membership row for the (GroupId, UserId) pair and throws NotFoundException if none exists — the 404 message names both ids. It distinguishes 'not a member' from 'group missing' by including both ids in the text.

Solutions

  1. Treat 404 as success in idempotent removal flows (desired end state already reached)
  2. Check membership via GET /groups/{groupId}/members before removing
  3. Verify you didn't swap the groupId and userId path segments

Example fix

// before
await api.RemoveUserFromGroup(groupId, userId); // throws on retry
// after
try { await api.RemoveUserFromGroup(groupId, userId); }
catch (ApiException e) when (e.Status == 404) { /* already removed */ }
Defensive patterns

Strategy: try-catch

Validate before calling

const members = await api.GetGroupMembers(groupId);
if (!members.some(m => m.userId === userId)) return; // nothing to remove

Try / catch

try { await api.RemoveUserFromGroup(groupId, userId); }
catch (ApiException e) when (e.Status == 404) { /* idempotent: not a member */ }

Prevention

When it happens

Trigger: DELETE /groups/{groupId}/users/{userId} where the user was never added, already removed (double delete), or either id is cross-tenant/invalid.

Common situations: Idempotent retry of a remove that already succeeded; removing a user whose membership row was cascaded away when the group or user was deleted; swapped groupId/userId in the URL.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/ed22b153710bc553. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Groups/RemoveUserFromGroup/RemoveUserFromGroupCommandHandler.cs:31

    private readonly IUserPermissionService _userPermissionService;

    public RemoveUserFromGroupCommandHandler(IdentityDbContext dbContext, IUserPermissionService userPermissionService)
    {
        _dbContext = dbContext;
        _userPermissionService = userPermissionService;
    }

    public async ValueTask<Unit> Handle(RemoveUserFromGroupCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        var membership = await _dbContext.UserGroups
            .Include(ug => ug.Group)
            .FirstOrDefaultAsync(ug => ug.GroupId == command.GroupId && ug.UserId == command.UserId, cancellationToken);

        if (membership is null)
        {
            throw new NotFoundException($"User '{command.UserId}' is not a member of group '{command.GroupId}'.");
        }

        // Default groups (e.g. seeded "All Users") require every tenant user to be a member, so
        // removing one breaks that invariant and leaves later registrants in a half-populated group.
        if (membership.Group is not null && membership.Group.IsDefault)
        {
            throw new ForbiddenException("Users cannot be removed from a default group.");
        }

        _dbContext.UserGroups.Remove(membership);
        await _dbContext.SaveChangesAsync(cancellationToken);

        // Leaving a group may revoke roles the user only held through this group —
        // invalidate so the cached permission set is rebuilt on next request.
        await _userPermissionService.InvalidatePermissionCacheAsync(command.UserId, cancellationToken).ConfigureAwait(false);

        return Unit.Value;
    }

View on GitHub (pinned to 3f2959e683)