fullstackhero/dotnet-starter-kit · error · NotFoundException
Ticket not found.
Error message
Ticket {command.TicketId} not found. What it means
DeleteTicketCommandHandler looks up the ticket by command.TicketId and throws NotFoundException (404) if absent, before performing the soft delete (audit interceptor flips IsDeleted). A missing id means nothing was deleted.
Solutions
- Treat a second 404 on the same id as idempotent success in the client and refresh the list.
- Verify the ticket id exists before issuing the delete.
- Catch NotFoundException and log rather than crash in batch cleanup jobs.
- Confirm the caller's tenant matches the ticket's tenant.
Example fix
// before
deleteTicket.mutate(id); // second click throws NotFoundException
// after
const onDelete = (id: string) => deleteTicket.mutate(id, {
onError: (e) => { if (isNotFound(e)) queryClient.invalidateQueries(['tickets']); }
}); Defensive patterns
Strategy: validation
Validate before calling
var ticket = await dbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id);
if (ticket is null) throw new KeyNotFoundException($"Ticket {id} not found; nothing deleted."); Type guard
static bool Deletable(Ticket? t) => t is { IsDeleted: false }; Try / catch
try { await mediator.Send(new DeleteTicketCommand { TicketId = id }); }
catch (NotFoundException) { /* already gone — treat as idempotent success */ } Prevention
- Disable the Delete button after the first click to avoid duplicate calls.
- Treat repeated 404s on delete as success and re-sync the list.
- Verify tenant scope before cross-service deletes.
- Log soft-deleted ids in cleanup jobs rather than throwing.
When it happens
Trigger: DeleteTicketCommand with an unknown TicketId, an id already hard-removed, or a ticket invisible due to tenant filtering.
Common situations: Double-click Delete causing a second call after the first soft-deleted the ticket (if filtered out on re-query); cleanup scripts with stale ids; cross-tenant id collision.
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/5aec21fc4a864db2.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/DeleteTicket/DeleteTicketCommandHandler.cs:19
using FSH.Framework.Core.Exceptions;
using FSH.Modules.Tickets.Contracts.v1.Tickets;
using FSH.Modules.Tickets.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;
namespace FSH.Modules.Tickets.Features.v1.Tickets.DeleteTicket;
public sealed class DeleteTicketCommandHandler(TicketsDbContext dbContext)
: ICommandHandler<DeleteTicketCommand, Unit>
{
public async ValueTask<Unit> Handle(DeleteTicketCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
var ticket = await dbContext.Tickets
.FirstOrDefaultAsync(t => t.Id == command.TicketId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException($"Ticket {command.TicketId} not found.");
// Soft delete: the audit interceptor converts the EF Delete into an IsDeleted flip.
// Comments are not auto-included, so they are left untouched and survive a Restore.
dbContext.Tickets.Remove(ticket);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return Unit.Value;
}
}
View on GitHub (pinned to 3f2959e683)