{"record":{"id":"17d33ef83e8c1210","repo":"iOfficeAI/OfficeCLI","slug":"comment-already-exists-on-cmtrefupper-remove-it","errorCode":null,"errorMessage":"comment already exists on {cmtRefUpper}. Remove it first before adding a new comment.","messagePattern":"comment already exists on (.+?)\\. Remove it first before adding a new comment\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Add.Tables.cs","lineNumber":285,"sourceCode":"        if (commentsPart.Comments == null)\n        {\n            commentsPart.Comments = new Comments(\n                new Authors(new Author(cmtAuthor)),\n                new CommentList()\n            );\n        }\n\n        var comments = commentsPart.Comments;\n        var authors = comments.GetFirstChild<Authors>()!;\n        var commentList = comments.GetFirstChild<CommentList>()!;\n\n        // CONSISTENCY(overlap-reject): duplicate comment on the same\n        // cell is ambiguous — mirror the table T4 overlap-reject\n        // pattern. User must `remove comment` first to replace it.\n        var cmtRefUpper = cmtRef.ToUpperInvariant();\n        if (commentList.Elements<Comment>().Any(c =>\n                string.Equals(c.Reference?.Value, cmtRefUpper, StringComparison.OrdinalIgnoreCase)))\n            throw new ArgumentException(\n                $\"comment already exists on {cmtRefUpper}. Remove it first before adding a new comment.\");\n\n        uint authorId = 0;\n        var existingAuthors = authors.Elements<Author>().ToList();\n        var authorIdx = existingAuthors.FindIndex(a => a.Text == cmtAuthor);\n        if (authorIdx >= 0)\n            authorId = (uint)authorIdx;\n        else\n        {\n            authors.AppendChild(new Author(cmtAuthor));\n            authorId = (uint)existingAuthors.Count;\n        }\n\n        var comment = new Comment { Reference = cmtRef.ToUpperInvariant(), AuthorId = authorId };\n        // Support user-supplied `\\n` (literal two-char sequence from\n        // CLI) and real LF as line breaks — Excel renders the\n        // preserved newline in the comment body. Matches the shape\n        // `text` behavior documented in add-shape help.","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Add.Tables.cs#L267-L303","documentation":"Thrown by AddComment after the comments part is located. It upper-cases the target ref and scans the existing CommentList for any Comment whose Reference equals it (OrdinalIgnoreCase). A duplicate is ambiguous, so the handler mirrors its table-overlap T4 reject pattern and requires the caller to remove the old comment first.","triggerScenarios":"Call Add type \"comment\" targeting a cell (by ref property or path segment) that already has a Comment element in that sheet's WorksheetCommentsPart.","commonSituations":"Re-running an add script without idempotency; assuming add replaces; iterating over cells and hitting one already annotated in a prior pass.","solutions":["Remove the existing comment first (Remove on /Sheet/Cell or the comment path).","Target a different cell if you intend to keep the old comment.","Track annotated cells and skip them, or branch on existence before adding."],"exampleFix":"// before (cell already has a comment)\nhandler.Add(\"/Sheet1/B2\", \"comment\", null, new() { [\"text\"] = \"new\" });\n// after\nhandler.Remove(\"/Sheet1/B2/comment\");\nhandler.Add(\"/Sheet1/B2\", \"comment\", null, new() { [\"text\"] = \"new\" });","handlingStrategy":"try-catch","validationCode":"string cell = (properties.GetValueOrDefault(\"ref\") ?? pathCellSegment ?? \"\").ToUpperInvariant();\nvar node = handler.Get($\"/{sheet}\", 2);\n// treat any existing /Sheet/Cell/comment child as 'already present'\nif (node.Children.Any(c => c.Path.Equals($\"/{sheet}/{cell}/comment\", StringComparison.OrdinalIgnoreCase)))\n    throw new InvalidOperationException($\"comment already on {cell}\");","typeGuard":null,"tryCatchPattern":"try { handler.Add(\"/Sheet1/B2\", \"comment\", null, props); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"comment already exists on\"))\n{\n    handler.Remove(\"/Sheet1/B2/comment\");\n    handler.Add(\"/Sheet1/B2\", \"comment\", null, props); // replace\n}","preventionTips":["Track which cells you have annotated and skip or remove-first.","Make add-comment idempotent in your wrapper: remove-then-add when replacing.","Before batch runs, dump existing comments and subtract from your target set."],"tags":["excel","comment","collision","overlap","validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}