fullstackhero/dotnet-starter-kit · error · NotFoundException

Notification not found.

Error message

Notification not found.

What it means

MarkNotificationReadCommandHandler queries the notification by the pair (cmd.NotificationId, currentUserId) and throws NotFoundException("Notification not found.") when no row matches. Because the query filters by the caller's UserId, this is also returned when the notification exists but belongs to another user — existence is deliberately not leaked. Maps to HTTP 404.

Solutions

  1. Verify the NotificationId comes from a list query for the same authenticated user.
  2. Handle 404 gracefully in the client: remove the item from the local list and show a non-blocking message.
  3. Check the notification still exists in the DB: SELECT * FROM "Notifications" WHERE "Id" = '<id>' AND "UserId" = '<userId>'.
  4. Re-sync the notification list before retrying, and don't retry blindly on 404.

Example fix

// before
await mediator.Send(new MarkNotificationReadCommand(notificationId), ct); // 404 if stale
// after
try
{
    await mediator.Send(new MarkNotificationReadCommand(notificationId), ct);
}
catch (NotFoundException)
{
    queryClient.invalidateQueries(["notifications"]); // drop stale id, don't surface error
}
Defensive patterns

Strategy: try-catch

Validate before calling

var owned = notifications.some(n => n.id === notificationId);
if (!owned) { console.warn('Notification id not in current user\'s list; skipping.'); return; }

Type guard

bool IsOwnNotification(NotificationDto? n, string currentUserId) => n is not null && n.UserId == currentUserId;

Try / catch

try
{
    await mediator.Send(new MarkNotificationReadCommand(notificationId), ct);
}
catch (NotFoundException)
{
    // stale or foreign id — drop it locally, don't surface an error
    queryClient.invalidateQueries(["notifications"]);
}

Prevention

When it happens

Trigger: Passing a NotificationId that does not exist, an already-deleted notification, an id from another tenant/user, or a malformed/duplicated client-side id.

Common situations: Client cached a notification id that was later deleted; user A's session tries to mark user B's notification (id copied across accounts); stale list data after another device marked/deleted it; wrong environment's database.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/9c9d69e779288044. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Notifications/Modules.Notifications/Features/v1/MarkNotificationRead/MarkNotificationReadCommandHandler.cs:27

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

        // Caller-scoped: filter by (Id, UserId) so users can only mutate their own rows. Returns
        // 404 if the row exists but belongs to someone else — we don't leak existence.
        var notification = await db.Notifications
            .FirstOrDefaultAsync(n => n.Id == cmd.NotificationId && n.UserId == currentUserId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("Notification not found.");

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

View on GitHub (pinned to 3f2959e683)