fullstackhero/dotnet-starter-kit · error · NotFoundException
Channel not found.
Error message
Channel not found.
What it means
ChannelAuthorization.RequireMember throws NotFoundException("Channel not found.") when the requested userId is not found among channel.Members. The comment explains this is intentional: non-members get 404 instead of 403 Forbidden so they cannot probe whether a channel exists. Thus this error means 'you are not a member', not necessarily 'the channel is missing'.
Solutions
- Add the user as a ChannelMember of the channel before performing channel-scoped operations.
- Have the caller first verify membership (list user's channels) and skip the operation if absent.
- Check whether a membership removal/invite flow ran unexpectedly; re-create the membership row if it was lost.
- Do not 'fix' by downgrading to Forbidden — the 404 masking is a deliberate anti-enumeration measure.
Example fix
// before
channel.RequireMember(userId); // throws if not a member
// after
if (channel.Members.Any(m => m.UserId == userId))
{
channel.RequireMember(userId);
}
else
{
channel.Join(userId); // establish membership first
} Defensive patterns
Strategy: validation
Validate before calling
const myChannels = await api.listMyChannels();
if (!myChannels.some(c => c.id === channelId)) {
throw new Error("not a member of this channel"); // skip the operation
} Try / catch
try {
await api.deleteMessage(messageId);
} catch (e) {
if (e.status === 404) {
// 404 here may mean "not a member" — treat as access denied, not missing data
showNoAccessNotice();
} else throw e;
} Prevention
- Check membership before offering channel-scoped actions in the UI.
- Refresh membership state after kick/leave events.
- Remember 404 is intentional masking for non-members — don't distinguish.
- Ensure invite flows complete before the user can act on the channel.
When it happens
Trigger: Any handler calling channel.RequireMember(userId) (e.g. DeleteMessage, EditMessage) where the authenticated user has no ChannelMember row for that channel — never joined, was removed, or membership belongs to another tenant.
Common situations: A user attempting to edit/delete messages in a channel they were removed from; a user from another workspace guessing channel/message ids; membership seeded only after an invite event that has not been processed yet.
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
- User is not a member.
- Direct messages have fixed membership.
- User is already a member.
- Only the author can edit a message.
- Only the author or a moderator can delete.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/b61e203388394eb9.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Chat/Modules.Chat/Features/v1/Internal/ChannelAuthorization.cs:17
using FSH.Framework.Core.Exceptions;
using FSH.Modules.Chat.Contracts.v1.DTOs;
using FSH.Modules.Chat.Domain;
namespace FSH.Modules.Chat.Features.v1.Internal;
/// <summary>
/// Small assertion helpers used by channel/message handlers so the rules stay in one place.
/// Throws framework-aware exceptions so the global handler emits the right HTTP status.
/// </summary>
internal static class ChannelAuthorization
{
public static ChannelMember RequireMember(this ChatChannel channel, string userId)
{
var member = channel.Members.FirstOrDefault(m => string.Equals(m.UserId, userId, StringComparison.Ordinal));
// Use NotFoundException (404) instead of Forbidden so non-members can't probe channel existence.
return member ?? throw new NotFoundException("Channel not found.");
}
public static ChannelMember RequireAdmin(this ChatChannel channel, string userId)
{
var member = channel.RequireMember(userId);
if (member.Role != ChannelMemberRole.Admin)
{
throw new ForbiddenException("Channel admin role required.");
}
return member;
}
}
View on GitHub (pinned to 3f2959e683)