{"record":{"id":"e79bdb1fe281ed50","repo":"fullstackhero/dotnet-starter-kit","slug":"a-closed-ticket-cannot-accept-new-comments-reopen-it-first","errorCode":null,"errorMessage":"A closed ticket cannot accept new comments — reopen it first.","messagePattern":"A closed ticket cannot accept new comments — reopen it first\\.","errorType":"exception","errorClass":"CustomException","httpStatus":409,"severity":"error","filePath":"src/Modules/Tickets/Modules.Tickets/Domain/Ticket.cs","lineNumber":201,"sourceCode":"        {\n            return;\n        }\n\n        // Clear the close timestamp so a fresh resolution gets its own audit window.\n        ClosedAtUtc = null;\n        ResolvedAtUtc = null;\n        ResolutionNote = null;\n        UpdatedAtUtc = DateTime.UtcNow;\n        // Reopened tickets fall back to whatever assignment state they had:\n        // if still assigned, InProgress; if not, Open.\n        TransitionStatus(AssignedToUserId is null ? TicketStatus.Open : TicketStatus.InProgress);\n    }\n\n    public Guid AddComment(Guid authorUserId, string body)\n    {\n        if (Status == TicketStatus.Closed)\n        {\n            throw new CustomException(\n                \"A closed ticket cannot accept new comments — reopen it first.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.Conflict);\n        }\n\n        var comment = TicketComment.Create(Id, authorUserId, body);\n        _comments.Add(comment);\n        UpdatedAtUtc = DateTime.UtcNow;\n\n        AddDomainEvent(DomainEvent.Create<TicketCommentAddedDomainEvent>(\n            (id, ts) => new TicketCommentAddedDomainEvent(Id, comment.Id, authorUserId, id, ts)));\n\n        return comment.Id;\n    }\n\n    private void TransitionStatus(TicketStatus next)\n    {\n        if (next == Status)","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Tickets/Modules.Tickets/Domain/Ticket.cs#L183-L219","documentation":"Ticket.AddComment rejects new comments on a Closed ticket, throwing CustomException with HTTP 409 Conflict. Commenting is only allowed while the ticket is active; it must be reopened to continue the discussion. This guards the closed ticket's frozen conversation thread.","triggerScenarios":"Calling AddComment (or the add-comment endpoint) on a ticket with Status == TicketStatus.Closed, e.g. replying from a stale comment form after the ticket was closed.","commonSituations":"User kept a comment box open while an agent closed the ticket; email/automation piping replies into closed tickets; UI not refreshing ticket status after close.","solutions":["Reopen the ticket, then add the comment.","Handle 409 in the client, refresh the ticket, and show 'Reopen to comment'.","Check ticket status when loading the comment form and hide/disable it for closed tickets.","If the reply must be preserved, reopen the ticket programmatically before submitting the comment."],"exampleFix":"// before\nticket.AddComment(authorUserId, body); // 409 if closed\n// after\nif (ticket.Status == TicketStatus.Closed)\n{\n    ticket.Reopen();\n}\nticket.AddComment(authorUserId, body);","handlingStrategy":"validation","validationCode":"if (ticket.status === TicketStatus.Closed) {\n  throw new Error('Reopen the ticket to comment.');\n}\nticket.addComment(authorUserId, body);","typeGuard":"bool CanComment(Ticket t) => t.Status != TicketStatus.Closed;","tryCatchPattern":"try\n{\n    await api.addComment(ticketId, body);\n}\ncatch (err) {\n  if (err.status === 409) {\n    await api.reopen(ticketId);\n    await api.addComment(ticketId, body);\n  } else { throw err; }\n}","preventionTips":["Hide/disable the comment box for closed tickets and refresh status when the detail view regains focus.","Warn users their reply may hit a closed ticket before submitting after long idle.","Treat 409 as 'reopen to comment' and surface that action in the UI.","For email/automation integrations, check status before piping replies into comments."],"tags":["tickets","domain-rules","conflict","state-machine"],"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"}