{"record":{"id":"17913e9f0c88d8e6","repo":"fullstackhero/dotnet-starter-kit","slug":"channel-not-found","errorCode":null,"errorMessage":"Channel not found.","messagePattern":"Channel not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Features/v1/Channels/AddChannelMembers/AddChannelMembersCommandHandler.cs","lineNumber":29,"sourceCode":"\nnamespace FSH.Modules.Chat.Features.v1.Channels.AddChannelMembers;\n\npublic sealed class AddChannelMembersCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IHubContext<AppHub> hub)\n    : ICommandHandler<AddChannelMembersCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(AddChannelMembersCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");\n        var currentUserId = userId.ToString();\n\n        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == cmd.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Channel not found.\");\n\n        // Members can invite to public channels they belong to; private channels require Admin.\n        var caller = channel.RequireMember(currentUserId);\n        if (channel.IsPrivate && caller.Role != ChannelMemberRole.Admin)\n        {\n            throw new ForbiddenException(\"Only channel admins can add members to private channels.\");\n        }\n\n        var newlyAdded = new List<string>();\n        foreach (var uid in cmd.UserIds.Distinct(StringComparer.Ordinal))\n        {\n            // Skip duplicates silently — endpoint is idempotent for already-members.\n            if (channel.Members.Any(m => string.Equals(m.UserId, uid, StringComparison.Ordinal))) continue;\n            channel.AddMember(uid, currentUserId);\n            newlyAdded.Add(uid);\n        }\n\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Channels/AddChannelMembers/AddChannelMembersCommandHandler.cs#L11-L47","documentation":"AddChannelMembersCommandHandler throws NotFoundException when no channel with cmd.ChannelId exists in db.Channels. The lookup uses FirstOrDefaultAsync and throws instead of returning null so the endpoint maps to 404.","triggerScenarios":"Posting AddChannelMembers with a ChannelId that was never created, was archived/deleted (soft-delete removes it from queries), or a GUID typo / channel from another environment.","commonSituations":"Client cached a channel ID after it was archived; copy-pasted ID from another tenant/database; frontend using an undefined/stale variable as the ID.","solutions":["Verify the ChannelId exists (and isn't archived) before inviting members","Re-fetch the channel list instead of using a cached ID","Log the incoming ChannelId and confirm it against the database"],"exampleFix":"// before\nawait addMembers(channelIdFromCache, userIds);\n// after\nvar channel = await getChannel(channelId);\nif (channel) await addMembers(channelId, userIds);","handlingStrategy":"validation","validationCode":"var exists = await db.Channels.AnyAsync(c => c.Id == cmd.ChannelId);\nif (!exists) return 404; // before inviting","typeGuard":"bool channelExists = channel is not null;","tryCatchPattern":"try { await addMembers(channelId, userIds); }\ncatch (NotFoundException) { /* refresh channel list; remove stale entry */ }","preventionTips":["Re-fetch channels instead of caching IDs long-term","Validate GUIDs client-side before submit","Treat 404 as 'channel gone' and clear local cache"],"tags":["chat","not-found","channel","rest"],"backgroundTag":"resource-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}