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
- Verify the ChannelId exists: query db.Channels for the id in the same tenant before updating.
- Check tenant isolation — confirm the request's tenant header/claim matches the tenant that owns the channel.
- Refresh client state: re-fetch the channel list and use a current id.
- 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
- Invalidate channel caches after delete operations.
- Re-fetch channels when switching tenants.
- Never persist channel ids across workspace switches.
- Verify seed data covers the tenant under test.
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)