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

  1. Add the user as a ChannelMember of the channel before performing channel-scoped operations.
  2. Have the caller first verify membership (list user's channels) and skip the operation if absent.
  3. Check whether a membership removal/invite flow ran unexpectedly; re-create the membership row if it was lost.
  4. 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

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


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)