{"record":{"id":"fa41a224f2ef2b69","repo":"fullstackhero/dotnet-starter-kit","slug":"a-closed-ticket-cannot-be-edited-reopen-it-first","errorCode":null,"errorMessage":"A closed ticket cannot be edited — reopen it first.","messagePattern":"A closed ticket cannot be edited — reopen it first\\.","errorType":"exception","errorClass":"CustomException","httpStatus":409,"severity":"error","filePath":"src/Modules/Tickets/Modules.Tickets/Domain/Ticket.cs","lineNumber":168,"sourceCode":"                (IEnumerable<string>?)null,\n                HttpStatusCode.Conflict);\n        }\n\n        ClosedAtUtc = DateTime.UtcNow;\n        UpdatedAtUtc = DateTime.UtcNow;\n        TransitionStatus(TicketStatus.Closed);\n    }\n\n    /// <summary>\n    /// Edits the mutable details of an open/in-progress/resolved ticket. A closed ticket is\n    /// frozen — it must be reopened first.\n    /// </summary>\n    public void UpdateDetails(string title, string? description, TicketPriority priority)\n    {\n        ArgumentException.ThrowIfNullOrWhiteSpace(title);\n        if (Status == TicketStatus.Closed)\n        {\n            throw new CustomException(\n                \"A closed ticket cannot be edited — reopen it first.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.Conflict);\n        }\n\n        Title = title.Trim();\n        Description = string.IsNullOrWhiteSpace(description) ? null : description.Trim();\n        Priority = priority;\n        UpdatedAtUtc = DateTime.UtcNow;\n    }\n\n    public void Reopen()\n    {\n        if (Status is TicketStatus.Open or TicketStatus.InProgress)\n        {\n            return;\n        }\n","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Tickets/Modules.Tickets/Domain/Ticket.cs#L150-L186","documentation":"Ticket.UpdateDetails refuses to edit a Closed ticket, throwing CustomException with HTTP 409 Conflict. Closed tickets are immutable in this domain; they must be reopened before title, description, or priority can change. The title argument is also validated as non-empty before this check.","triggerScenarios":"Calling UpdateDetails (or the update-ticket endpoint) on a ticket with Status == TicketStatus.Closed.","commonSituations":"Editing an old ticket from a stale browser tab after someone closed it; concurrent edits where close won the race; automated bulk-update jobs not checking status.","solutions":["Reopen the ticket first, then apply the update.","Reload the ticket to refresh Status before editing.","Handle 409 in the client and offer a 'Reopen and edit' action.","Disable edit controls in the UI when the ticket status is Closed."],"exampleFix":"// before\nticket.UpdateDetails(title, description, priority); // 409 if closed\n// after\nif (ticket.Status == TicketStatus.Closed)\n{\n    ticket.Reopen();\n}\nticket.UpdateDetails(title, description, priority);","handlingStrategy":"validation","validationCode":"if (ticket.status === TicketStatus.Closed) {\n  throw new Error('Reopen the ticket before editing.');\n}\nif (!title?.trim()) {\n  throw new Error('Title is required.');\n}\nticket.updateDetails(title, description, priority);","typeGuard":"bool CanEdit(Ticket t) => t.Status != TicketStatus.Closed && !string.IsNullOrWhiteSpace(t.Title);","tryCatchPattern":"try\n{\n    ticket.UpdateDetails(title, description, priority);\n}\ncatch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Conflict)\n{\n    ticket.Reopen();\n    ticket.UpdateDetails(title, description, priority);\n}","preventionTips":["Disable the edit form when ticket status is Closed.","Re-fetch ticket status before submitting edits from long-open forms.","Treat 409 as 'ticket was closed meanwhile' and offer Reopen-and-edit.","Trim and require the title client-side to fail fast before hitting the aggregate."],"tags":["tickets","domain-rules","conflict","immutability"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}