{"record":{"id":"936c2517322a61a5","repo":"fullstackhero/dotnet-starter-kit","slug":"cannot-edit-a-deleted-message","errorCode":null,"errorMessage":"Cannot edit a deleted message.","messagePattern":"Cannot edit a deleted message\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Domain/Message.cs","lineNumber":83,"sourceCode":"                m._mentions.Add(MessageMention.Create(m.Id, pm.MentionedUserId, pm.StartIndex, pm.Length));\n            }\n        }\n        m.AddDomainEvent(DomainEvent.Create((id, ts) =>\n            new MessageCreatedDomainEvent(channelId, m.Id, authorUserId, parentMessageId, id, ts)));\n        return m;\n    }\n\n    /// <summary>\n    /// Input shape for <see cref=\"Create\"/>: a resolved mention with the original position range\n    /// in the body so the UI can render the highlight without re-parsing.\n    /// </summary>\n    public readonly record struct ParsedMention(string MentionedUserId, int StartIndex, int Length);\n\n    public void Edit(string newBody, string editingUserId)\n    {\n        if (DeletedAtUtc.HasValue)\n        {\n            throw new InvalidOperationException(\"Cannot edit a deleted message.\");\n        }\n        if (!string.Equals(AuthorUserId, editingUserId, StringComparison.Ordinal))\n        {\n            throw new InvalidOperationException(\"Only the author can edit a message.\");\n        }\n        ArgumentException.ThrowIfNullOrWhiteSpace(newBody);\n\n        Body = newBody.Trim();\n        EditedAtUtc = DateTime.UtcNow;\n        AddDomainEvent(DomainEvent.Create((id, ts) =>\n            new MessageEditedDomainEvent(ChannelId, Id, AuthorUserId, id, ts)));\n    }\n\n    public void SoftDelete(string deletingUserId, bool isModerator)\n    {\n        if (DeletedAtUtc.HasValue) return;\n        if (!isModerator && !string.Equals(AuthorUserId, deletingUserId, StringComparison.Ordinal))\n        {","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Domain/Message.cs#L65-L101","documentation":"Message.Edit refuses to mutate a soft-deleted message: once DeletedAtUtc is set the message body is cleared and the aggregate is effectively a tombstone, so editing throws InvalidOperationException. Deleted state is terminal for edits in this domain model.","triggerScenarios":"Calling message.Edit(newBody, editingUserId) on a message whose DeletedAtUtc is non-null — e.g. an edit command racing a concurrent delete, or a client editing a message it still sees cached after deletion.","commonSituations":"Two tabs open: one deletes, the other edits; stale client cache after a delete event that the client missed; handlers that don't re-load the message before editing; replayed/out-of-order integration events.","solutions":["Check message.DeletedAtUtc is null before calling Edit (or expose IsDeleted) and reject the edit with a friendly error.","Catch InvalidOperationException in the handler and return 409 Conflict ('message already deleted').","Reload the message inside the command handler so the deleted state is current; rely on concurrency tokens for the race with delete.","Update clients on MessageDeleted events so they disable editing for deleted messages."],"exampleFix":"// before\nmessage.Edit(request.NewBody, currentUserId);\n// after\nif (message.DeletedAtUtc is not null)\n{\n    throw new ConflictException(\"Cannot edit a deleted message.\");\n}\nmessage.Edit(request.NewBody, currentUserId);","handlingStrategy":"validation","validationCode":"public static bool CanEdit(Domain.Message m) => m.DeletedAtUtc is null;","typeGuard":"if (message is { DeletedAtUtc: not null }) throw new ConflictException(\"Cannot edit a deleted message.\");","tryCatchPattern":"try { message.Edit(newBody, userId); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"deleted\")) { throw new ConflictException(ex.Message); }","preventionTips":["Check DeletedAtUtc/IsDeleted before enabling edit in the UI","Subscribe to MessageDeleted events to update client state","Re-load the message in the handler before editing","Disable edit actions on tombstoned messages"],"tags":["domain","chat","edit","soft-delete","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"}