Proposed fix: render add-pr-comment inline threads as applyable single/multi-line suggestions
I have a fix ready on a fork branch but cannot open a PR directly (this repo limits PR creation to collaborators). The branch is rebased on current main and "Able to merge":
A maintainer can open the PR from that compare link, or pull/cherry-pick the three commits below. Happy to be added as a collaborator so I can open it myself.
Problem
The add-pr-comment safe output produces inline suggestion threads that Azure DevOps renders as broken, non-applyable comments. There are three distinct facets of the same problem, each demonstrated live against real ADO PRs.
1. Zero-width anchor → concatenated / duplicated line
The inline thread is anchored with rightFileEnd at offset 1, i.e. a zero-width range (L184:1 → L184:1). ADO treats a zero-width anchor as an insertion point, so applying a single-line suggestion prepends the suggested text to the original line instead of replacing it:
Mock.Of<IProgress>(MockBehavior.Loose), … Progress = Mock.Of<IProgress>(),
Fix: anchor rightFileEnd to (UTF-16 length of the last covered line) + 1 (CRLF-safe) so the range covers the whole line(s) and Apply performs a clean replace. Adds a pure inline_thread_context() helper plus line_end_offset(), with unit tests for the single- and multi-line cases.
2. Fenced suggestion body HTML-escaped
sanitize() → escape_html_tags escapes all </>, including the contents of the ```suggestion fence, so applying the suggestion writes literal </> into the file. Fix: escape_html_tags is now fence-aware — lines inside ```/~~~ fences pass through verbatim, matching how every Markdown renderer (including ADO) treats fenced code.
3. Inline `code` spans in prose HTML-escaped
The same escaper also mangles inline-code spans in the comment prose, e.g. `Mock.Of<IProgress>()` renders as Mock.Of<IProgress>(). Fix: escape_html_tags now also preserves backtick-delimited inline code. A run of k backticks is closed only by a run of exactly k backticks on the same line; an unmatched backtick is treated as a literal and the remainder of the line is still escaped, so HTML cannot evade neutralization.
Why skipping the escape is safe
Markdown renderers (including ADO) render fenced and inline code literally and never interpret HTML inside them, so leaving </> un-escaped there matches renderer behaviour — any smuggled HTML stays inert. Backtick-run matching slices only at backtick (ASCII 0x60) boundaries, so it is byte-safe for multi-byte UTF-8 content.
Validation
cargo test --bins sanitize — 77 passed (5 new tests across the three fixes).
cargo clippy --bins — clean.
cargo fmt --check — clean.
- Demonstrated end-to-end by running the patched
ado-aw execute against real ADO PRs:
- Single + multi-line: anchors
L1:1→L1:78 and L1:1→L2:49, literal <...>, one-click Apply.
- Worst-case production repro (Moq1400, line 184): anchor
L184:1→L184:45, clean whole-line replace, literal <...> in both the prose inline-code spans and the suggestion body, no <.
Commits
65268f2 feat(safe-outputs): render add-pr-comment inline threads as applyable suggestions
4764358 fix(safe-outputs): preserve fenced code blocks when escaping HTML
01a0096 fix(safe-outputs): preserve inline code spans when escaping HTML
Files touched
src/safeoutputs/add_pr_comment.rs — anchor / inline_thread_context() / line_end_offset() + tests + agent-facing schema docs.
src/sanitize.rs — fence-aware and inline-code-aware escape_html_tags + tests.
docs/safe-outputs.md — "Inline applyable suggestions" section.
Proposed fix: render
add-pr-commentinline threads as applyable single/multi-line suggestionsI have a fix ready on a fork branch but cannot open a PR directly (this repo limits PR creation to collaborators). The branch is rebased on current
mainand "Able to merge":YuliiaKovalova:fix/pr-comment-suggestion-anchorA maintainer can open the PR from that compare link, or pull/cherry-pick the three commits below. Happy to be added as a collaborator so I can open it myself.
Problem
The
add-pr-commentsafe output produces inlinesuggestionthreads that Azure DevOps renders as broken, non-applyable comments. There are three distinct facets of the same problem, each demonstrated live against real ADO PRs.1. Zero-width anchor → concatenated / duplicated line
The inline thread is anchored with
rightFileEndat offset1, i.e. a zero-width range (L184:1 → L184:1). ADO treats a zero-width anchor as an insertion point, so applying a single-line suggestion prepends the suggested text to the original line instead of replacing it:Fix: anchor
rightFileEndto(UTF-16 length of the last covered line) + 1(CRLF-safe) so the range covers the whole line(s) and Apply performs a clean replace. Adds a pureinline_thread_context()helper plusline_end_offset(), with unit tests for the single- and multi-line cases.2. Fenced suggestion body HTML-escaped
sanitize()→escape_html_tagsescapes all</>, including the contents of the```suggestionfence, so applying the suggestion writes literal</>into the file. Fix:escape_html_tagsis now fence-aware — lines inside```/~~~fences pass through verbatim, matching how every Markdown renderer (including ADO) treats fenced code.3. Inline
`code`spans in prose HTML-escapedThe same escaper also mangles inline-code spans in the comment prose, e.g.
`Mock.Of<IProgress>()`renders asMock.Of<IProgress>(). Fix:escape_html_tagsnow also preserves backtick-delimited inline code. A run of k backticks is closed only by a run of exactly k backticks on the same line; an unmatched backtick is treated as a literal and the remainder of the line is still escaped, so HTML cannot evade neutralization.Why skipping the escape is safe
Markdown renderers (including ADO) render fenced and inline code literally and never interpret HTML inside them, so leaving
</>un-escaped there matches renderer behaviour — any smuggled HTML stays inert. Backtick-run matching slices only at backtick (ASCII0x60) boundaries, so it is byte-safe for multi-byte UTF-8 content.Validation
cargo test --bins sanitize— 77 passed (5 new tests across the three fixes).cargo clippy --bins— clean.cargo fmt --check— clean.ado-aw executeagainst real ADO PRs:L1:1→L1:78andL1:1→L2:49, literal<...>, one-click Apply.L184:1→L184:45, clean whole-line replace, literal<...>in both the prose inline-code spans and the suggestion body, no<.Commits
65268f2feat(safe-outputs): render add-pr-comment inline threads as applyable suggestions4764358fix(safe-outputs): preserve fenced code blocks when escaping HTML01a0096fix(safe-outputs): preserve inline code spans when escaping HTMLFiles touched
src/safeoutputs/add_pr_comment.rs— anchor /inline_thread_context()/line_end_offset()+ tests + agent-facing schema docs.src/sanitize.rs— fence-aware and inline-code-awareescape_html_tags+ tests.docs/safe-outputs.md— "Inline applyable suggestions" section.