fullstackhero/dotnet-starter-kit · error · NotFoundException

Channel not found.

Error message

Channel not found.

What it means

UpdateChannelCommandHandler throws NotFoundException("Channel not found.") when no ChatChannel with cmd.ChannelId exists in the database (FirstOrDefaultAsync returns null). It is the module's standard 404 for a missing aggregate. Note the same message is also used deliberately to mask authorization failures downstream, so it does not always mean the row is absent.

Solutions

  1. Verify the ChannelId exists: query db.Channels for the id in the same tenant before updating.
  2. Check tenant isolation — confirm the request's tenant header/claim matches the tenant that owns the channel.
  3. Refresh client state: re-fetch the channel list and use a current id.
  4. If the channel should exist, check the DbMigrator/seed ran and the record was not deleted.

Example fix

// before
await api.updateChannel({ channelId: staleId, name });

// after
const channels = await api.listChannels();
const current = channels.find(c => c.id === staleId);
if (current) await api.updateChannel({ channelId: current.id, name });
Defensive patterns

Strategy: validation

Validate before calling

const channel = channelsQuery.data?.find(c => c.id === channelId);
if (!channel) return; // don't call update for unknown channel
if (channel.tenantId !== currentTenantId) return; // tenant mismatch guard

Type guard

function channelExists(id: string | undefined): id is string {
  return typeof id === "string" && id.length > 0 && knownChannelIds.has(id);
}

Try / catch

try {
  await api.updateChannel({ channelId, name });
} catch (e) {
  if (e.status === 404) { invalidateChannelsQuery(); showChannelGone(); }
  else throw e;
}

Prevention

When it happens

Trigger: PUT/POST to the update-channel endpoint with a ChannelId that was deleted, never existed, or is filtered out by the tenant query filter (channel belongs to another tenant).

Common situations: Client holding a stale channel id after the channel was deleted; cross-tenant id reuse (channel exists globally but not in the caller's tenant); typo'd or truncated id from client-side state; test fixtures seeding channels into a different tenant.

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/6dd426628ce628a0. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Chat/Modules.Chat/Features/v1/Channels/UpdateChannel/UpdateChannelCommandHandler.cs:24

using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Chat.Features.v1.Channels.UpdateChannel;

public sealed class UpdateChannelCommandHandler(
    ChatDbContext db,
    ICurrentUser currentUser)
    : ICommandHandler<UpdateChannelCommand, Unit>
{
    public async ValueTask<Unit> Handle(UpdateChannelCommand cmd, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(cmd);
        var userId = currentUser.GetUserId();
        if (userId == Guid.Empty) throw new UnauthorizedException("no current user");

        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == cmd.ChannelId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("Channel not found.");

        channel.RequireAdmin(userId.ToString());
        channel.Rename(cmd.Name, cmd.Description);
        channel.SetPrivate(cmd.IsPrivate);

        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return Unit.Value;
    }
}

View on GitHub (pinned to 3f2959e683)