Skip to content

fix(ci): PR test selector could silently mis-attribute changed paths containing tabs or newlines - #19790

Draft
Ankit Jain (radical) wants to merge 6 commits into
microsoft:mainfrom
radical:radical-fix-selector-rename-handling
Draft

Ankit Jain (radical) wants to merge 6 commits into
microsoft:mainfrom
radical:radical-fix-selector-rename-handling

Conversation

@radical

@radical Ankit Jain (radical) commented Aug 28, 2026 •

Copy link
Copy Markdown
Member

What broke

The selective PR CI test-selector (tools/SelectTests) has two layers that independently parse git diff output: Layer 1 (GraphAffectedProjects.cs, MSBuild project graph) and Layer 2 (Program.cs, curated map). Layer 1 used git's default text-mode --name-status -M output; Layer 2 used a NUL-delimited -z parser. Git's text mode quote-escapes tabs/newlines/quotes/backslashes in a path, so a changed file containing one of those bytes never matched Layer 1's attribution index — the change silently failed to select its own project, the opposite of the selector's fail-safe design.

While tracing this (from PR #19486, CI run 33197019835), a few adjacent gaps surfaced in the same code:

  • Layer 1 also unconditionally rewrote \ → / in every changed path, even though git never uses \ as a separator — a literal backslash in a filename could be corrupted into a fake directory boundary and mis-attributed to an unrelated project.
  • Raw changed-file paths (which can contain literal \r/\n/backticks) were interpolated into the job summary and PR comment Markdown unescaped.
  • A truncated -z stream (missing the final NUL) parsed as if it were valid instead of failing loudly.

Root cause

Layer 1 had its own hand-rolled parser, independent from and less safe than Layer 2's. The two layers had quietly drifted.

The fix

  • Layer 1 now issues the same git diff --name-status -M -z as Layer 2, and both parse it through one shared method, ParseNameStatusOutput (tools/SelectTests/Program.cs).
  • Removed Layer 1's \→/ path rewrite; both layers now trust git's raw /-separated paths, matching Layer 2's existing behavior.
  • Added an EscapeForDisplay helper and applied it everywhere a raw path is rendered into Markdown.
  • ParseNameStatusOutput now throws if a -z stream doesn't end with a NUL terminator.

Why this approach

A separate fix was also attempted and rejected: exempting a rename's old path from the run-all fallback when its new path was already present in the changed-file set. An audit found that unsafe — a directory-scoped glob can have consumers beyond the file that moved, and a rename landing on an ignored glob or dropped by the prefilter satisfies the exemption's presence check without anything evaluating the real destination. Both could silently under-select tests. The exemption was removed; renames remain un-special-cased, and an unmatched rename old path still forces ALL, same as a plain deletion. docs/ci/test-trigger-map.md documents this under "Rename handling."

Call-out

Replaying PR #19486's exact diff still selects ALL — that PR's original symptom isn't "fixed" here, and that's intentional: an unnecessary full run is safer than a silently skipped test. The actual bug fixed is the narrower NUL-safety/escaping issue above, found while tracing the rename investigation.

Testing

New coverage under tests/Infrastructure.Tests/TestTriggerMap/ (169 tests passing) covers: tab/newline-containing renamed paths attributed correctly by both layers, truncated -z streams failing loudly, Markdown escaping of raw paths, the backslash-corruption false-attribution case, and the rename-exemption-removed behavior — including a golden replay of PR #19486's real diff (base fd9bbf76b4 / head 1e46e48122335ba5e970a5f78777a44fe3e963d2) confirming it still forces ALL.

Fixes # (no tracked issue — found during investigation of #19486)

… path

PR microsoft#19486 renamed eng/scripts/aspire-skills-bundle.common.ps1 to
aspire-skills-bundles.common.ps1 (singular -> plural) and, in the same
commit, updated test-trigger-map.yml's path_rules to reference the new
plural name. CI run 33197019835 (head 1e46e48
3e963d2) selected ALL PR test projects + jobs, citing the old singular
path as an unmapped leftover:

    selects ALL PR test projects + jobs -- run-all fallback:
    'eng/scripts/aspire-skills-bundle.common.ps1' is neither
    Layer-1-owned nor matched by a Layer 2 rule

Root cause: Layer 2's changed-file diff used
`git diff --name-only --no-renames`, which decomposes a rename into a
plain delete (old path) + add (new path) and discards git's own rename
pairing. Once the map's rule was repointed at the new name in the same
commit, the decomposed old path matched nothing, so TestSelector.Select
treated it as a genuine unmapped leftover and forced the run-all
fallback -- even though the new path was already correctly matched.

Fix: Layer 2 now diffs with `git diff --name-status -M`, the same
format Layer 1 already uses, and threads the set of rename old-paths
into TestSelector.Select. An old path is still fully glob-matched like
any other changed path first (a cross-directory rename must still hit
the old directory's rule), but if nothing matches it, it is exempted
from forcing ALL instead of being treated as an unmapped leftover.
Exemption is additive-only: it can never remove a target a rule
matched, only skip forcing ALL when nothing did.

Scope, verified empirically against PR microsoft#19486's real diff: the
exemption only covers renames git's own `-M` detection recognizes
(default 50% content-similarity threshold). Of the PR's 3 renamed
scripts, 2 are git-detected renames (R95, R54) and are now correctly
exempted; the 3rd (verify-aspire-skills-bundle.ps1) was rewritten so
heavily in the same commit (~30% similarity) that git reports a plain
delete+add, not a rename -- it is correctly NOT exempted, and the PR's
selection still (deliberately) falls back to ALL for that one file.
Lowering git's similarity threshold to also catch that case was
considered and rejected: it risks pairing an unrelated delete+add as a
false rename, which could wrongly exempt a genuinely unmatched,
unrelated deletion from the fallback -- reintroducing the same class
of under-selection this fix is meant to avoid.

Also investigated and confirmed NOT to be a risk, before or after this
change: the old path incidentally matching some unrelated Layer 2 rule
does not suppress or replace whatever the new path's own match
contributes. Layer 2 matching is purely additive per changed path, so
an incidental old-path match adds its (irrelevant) targets alongside
the new path's real ones rather than instead of them -- see
RenameWhoseOldPathIncidentallyMatchesAnUnrelatedRuleStillAddsBothTargets.

Tests added under tests/Infrastructure.Tests/TestTriggerMap/:
- SelectTestsAcceptanceTests: RenameOldPathUnmatchedDoesNotForceRunAll,
  RenameOldPathStillMatchesOwnRuleAdditively
- SelectTestsCliTests: InPlaceRenameWithoutOwnMapEntryDoesNotForceRunAll,
  RenameWhoseOldPathIncidentallyMatchesAnUnrelatedRuleStillAddsBothTargets,
  RenameBelowGitSimilarityThresholdIsNotExemptedAndStillForcesRunAll
- TestTriggerMapTests: SameCommitRenameWithMapEntryMovedToNewPathSelects
  ExactTargetsWithoutEscalating (golden scenario against the real
  production map and Aspire.slnx)

docs/ci/test-trigger-map.md and test-trigger-selector-design.md updated
to describe the exemption and its git-similarity-threshold boundary.

Fixes the wasteful ALL-selection observed in
microsoft#19486 (CI run 33197019835)
without weakening the run-all fallback's safety for genuine unmapped
changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
Copilot AI balanced review requested due to automatic review settings August 28, 2026 21:58
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19790

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19790"

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates selective CI to distinguish Git-detected rename sources and avoid unnecessary full-matrix fallbacks.

Changes:

  • Parses Layer 2 changes using git diff --name-status -M.
  • Exempts unmatched rename-old paths from fallback selection.
  • Adds regression tests and documents rename-threshold behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/SelectTests/TestSelector.cs Adds rename-old-path exemption logic.
tools/SelectTests/Program.cs Parses Git rename metadata.
tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs Adds production-map regression coverage.
tests/Infrastructure.Tests/TestTriggerMap/SelectTestsCliTests.cs Adds Git integration scenarios.
tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs Tests selector exemption behavior.
docs/ci/test-trigger-selector-design.md Documents selector design changes.
docs/ci/test-trigger-map.md Documents rename fallback semantics.

Comment thread tools/SelectTests/TestSelector.cs Outdated
Comment thread tests/Infrastructure.Tests/TestTriggerMap/SelectTestsCliTests.cs Outdated
@radical Ankit Jain (radical) changed the title fix(ci): stop selector run-all fallback on a same-commit rename's old path fix(ci): PR test selector no longer forces a full run when a renamed file's CI rule also moves Aug 28, 2026
Resolves review findings from Claude Opus 5 (high) and GPT-5.6 Sol
(high) on the selector rename-handling fix.

Quoted-path parsing bug (GPT F2): `ResolveChangedFiles` parsed
`git diff --name-status -M` output by splitting on literal tabs and
newlines while only disabling non-ASCII quoting via
`-c core.quotePath=false`. Git's default quoting still C-style-escapes
tabs, newlines, quotes, and backslashes in paths, which would corrupt
attribution for any changed path containing those bytes. Switched to
`git diff --name-status -M -z`, which is NUL-terminated and never
quotes/escapes any byte (NUL cannot appear in a valid path), and
rewrote the parser to walk NUL-delimited tokens instead of splitting
on tabs/newlines. Added a regression test using a same-commit rename
with a literal tab byte in the path.

Stale comments and test doc clarifications (Opus F2/F4, GPT F3, Opus
F3/F5):
- Corrected a comment in `RenameOutOfMappedPathStillSelectsItsTests`
  that still described the old `--no-renames` approach.
- Added a clarifying note to `RenameBelowGitSimilarityThresholdIsNot-
  ExemptedAndStillForcesRunAll`'s docstring, cross-referencing the
  tests that actually fail on reversion of the exemption.
- Added a docstring note on the golden `TestTriggerMapTests` case
  acknowledging its coupling to the live map is by design.
- Wrapped a temp directory in try/finally with cleanup in
  `SameCommitRenameWithMapEntryMovedToNewPathSelectsExactTargets-
  WithoutEscalating`, matching the project's established convention.

Residual risk (both reviewers, F1): git's `-M` rename detection is a
content-similarity heuristic, not a semantic guarantee, so it can in
rare cases mispair two unrelated files that happen to be highly
similar. Raising the similarity threshold isn't viable — PR microsoft#19486's
own motivating rename was only detected at R054. Documented this as an
accepted, bounded limitation in both `TestSelector.cs` and
`docs/ci/test-trigger-map.md` rather than attempting a fragile
mitigation.

Layer 1's `GraphAffectedProjects.cs` has an analogous quoted-path
parsing gap, but it is untouched by this PR's diff and is left as a
follow-up rather than expanded scope here.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
Copilot AI review requested due to automatic review settings August 28, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

tools/SelectTests/TestSelector.cs:267

  • This exemption applies to every detected rename, not only the stated case where the map rule moves with the file. For example, if an otherwise-unmapped loose CI input is renamed to a path covered by ignore (or filtered out before Select), the new path contributes no target and this branch suppresses the old path's fail-safe, so the selector can return no tests where the previous delete+add handling returned ALL. Preserve the fallback unless the corresponding new path is demonstrably accounted for by a moved/equivalent rule (which requires retaining the old/new pairing and validating the mapping), rather than exempting old paths solely because Git labeled them as renames.
            if (renameOldPathSet.Contains(file))
            {
                continue;
            }

Comment thread tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs Outdated
…ing old path

The rename-old-path exemption from the run-all fallback was keyed only by
the old path: it trusted that "the rename's new path already carries
whatever the map says about this content moving" without ever checking
that the new path was actually part of the diff the selector evaluated.

If the caller's prefilter dropped the new path entirely (e.g. a rename
INTO a doc-only path matched by ci-skip-entirely-patterns.txt), nothing
downstream ever evaluated whether that destination's content needed CI,
yet the old path was still silently exempted from forcing ALL -- a
genuine under-selection risk flagged by automated review on PR microsoft#19790.

Fix: change the rename representation from a flat set of old paths to an
old->new dictionary, and require the paired new path be present in the
selector's (post-prefilter) changed-file set before exempting the old
side. When the new path is present but itself matches nothing, it
becomes its own unmatched leftover and correctly still forces ALL, so no
extra check of what it resolved to is needed -- only the "destination
silently dropped" case needed closing.

Also fixes a second review comment: a synthetic test's comment
incorrectly claimed "the real map ignores changes to itself" -- the real
eng/github-ci/test-trigger-map.yml has no such entry and instead routes
self-changes to test:Infrastructure.Tests via a path_rule.

Tests:
- tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs:
  reworked the two existing rename-exemption tests for the new
  dictionary-based API, and added
  RenameOldPathWithDestinationDroppedByPrefilterStillForcesRunAll (the
  regression test for the review-flagged gap) and
  RenameOldPathWithStillUnmatchedDestinationForcesRunAllViaDestination
  (the "self-corrects" case).
- tests/Infrastructure.Tests/TestTriggerMap/SelectTestsCliTests.cs: added
  InPlaceRenameToPrefilteredDestinationStillForcesRunAll, a real-git-repo
  regression test driving the CLI end to end for the same scenario, and
  fixed the misleading "real map ignores itself" comment.
- tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs:
  updated the golden real-map test and SelectWithRealMap helper to the
  dictionary-based API.

Ran the full TestTriggerMap-scoped suite: 165/165 passed (162 previously
+ 3 new). Ran the full Infrastructure.Tests suite: 702/715 passed; the 13
failures are pre-existing and environment-caused (ExtensionChangelogFinalizedWorkflowTests
and SplitTestProjectsTests), unrelated to this change and unaffected by it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
Copilot AI review requested due to automatic review settings August 28, 2026 22:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

tools/SelectTests/Program.cs:497

  • This malformed-record branch also silently returns a partial diff, allowing changed files to disappear from selection. Abort so CI fails visibly rather than potentially under-selecting tests.
                if (tokenIndex >= tokens.Length)
                {
                    break; // truncated/malformed output; nothing left to parse

Comment thread tools/SelectTests/Program.cs
Comment thread docs/ci/test-trigger-selector-design.md Outdated
…a undetected rename

Automated review on PR microsoft#19790 pointed out that the existing golden test
only replayed 2 of the 3 files PR microsoft#19486 actually renamed, so it could
pass while the PR's stated motivating scenario -- the literal PR microsoft#19486
diff -- still selects ALL today, for an undetected reason.

That's true: `verify-aspire-skills-bundle.ps1` was rewritten heavily
enough in the same commit that git's default -M50% similarity threshold
reports it as a plain delete+add, not a rename, so this fix's exemption
(keyed on git-detected renames) never applies to it. Reproduced directly
against the real PR microsoft#19486 commits with the fixed selector
(`--skip-layer1 --enforce`): the run still selects ALL, citing that file
as the sole unattributed leftover.

Add a companion golden test that replays PR microsoft#19486's complete three-path
diff (not just the two git-detected renames) against the real production
map, asserting the fallback still fires for the undetected file. This
makes the existing, correct scope limitation explicit and falsifiable
instead of only described in a comment. Also tightens the existing golden
test's comment to point at the new test by name.

No production code changes; this is test/documentation scope
clarification only, following up on automated PR review feedback.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
Copilot AI review requested due to automatic review settings August 28, 2026 22:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

tests/Infrastructure.Tests/TestTriggerMap/SelectTestsAcceptanceTests.cs:360

  • This comment records within-branch review history rather than the enduring regression condition. Remove the current PR/review reference so the test remains understandable after merge.
        // Regression test for PR #19790 review feedback: the exemption above must require the rename's

tests/Infrastructure.Tests/TestTriggerMap/SelectTestsCliTests.cs:1068

  • This comment records within-branch review history rather than the enduring regression condition. Remove the current PR/review reference so the test remains understandable after merge.
    // Regression test for PR #19790 review feedback: the rename-old-path exemption above must not fire

docs/ci/test-trigger-selector-design.md:308

  • This design contract omits the implemented requirement that the paired destination survive the prefilter. It therefore describes a broader exemption than TestSelector actually applies and incorrectly states it cannot under-select despite the false-rename limitation documented in test-trigger-map.md. Document the destination-membership guard and avoid the absolute safety claim.
   the prefilter in step 1, so they never reach this fallback. **Exception:** the
   old path of a git-detected rename (see [Changed paths](#changed-paths)) that
   matches nothing is not treated as an unmapped leftover — a same-commit rename
   that also updates the map's own rule to the new name (as
   [microsoft/aspire#19486](https://github.com/microsoft/aspire/pull/19486) did)

tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs:515

  • “this PR fixes” narrates the current branch rather than the selector behavior that the committed test documents. Reword it as a durable statement of scope.
    // this PR fixes the in-place-rename-with-moved-map-entry mechanism, not git's own rename-similarity
    // detection, and does not claim to change the fallback outcome for content-heavy rewrite+renames.

Comment thread tools/SelectTests/Program.cs
Comment thread tools/SelectTests/TestSelector.cs Outdated
@radical Ankit Jain (radical) changed the title fix(ci): PR test selector no longer forces a full run when a renamed file's CI rule also moves fix(ci): PR test selector could silently mis-attribute changed paths containing tabs or newlines Aug 29, 2026
…afety gap

An earlier commit on this branch exempted a rename's old path from the
selector's run-all fallback when its new path was already present in
the changed-file set. An audit found that exemption unsafe: a rename
out of a directory-scoped glob (e.g. tests/Shared/Logging/**) can leave
other consumers of that glob with no signal, and a rename whose new
path is ignored or dropped by the prefilter satisfied the exemption's
presence check without anything ever evaluating the destination. Both
cases could silently under-select tests. Remove the exemption; renames
are intentionally un-special-cased again, and an unmatched rename old
path forces ALL like any other unaccounted-for change.

Separately, while tracing this, Layer 1's git-diff parser
(GraphAffectedProjects.GetChangedPathsFromGit) was found to still use
git's default text-mode --name-status output, split on raw
newline/tab bytes, while Layer 2 (Program.cs) already used a
NUL-delimited -z parser. Git's text mode double-quotes and
backslash-escapes any path containing a tab, newline, double quote, or
backslash; -c core.quotePath=false only suppresses non-ASCII-byte
escaping, not this class of path. A quoted/escaped path never matches
Layer 1's evaluated-item index or directory-containment fallback, so
it silently fails to attribute to its owning project -- an
under-selection risk, not a fail-safe one.

Layer 1 now issues the same `git diff --name-status -M -z` Layer 2
uses, and both layers parse it through one shared method
(Selection.ParseNameStatusOutput) instead of two independently
maintained parsers that had drifted.

Net effect for renames: replaying PR microsoft#19486's exact diff still selects
ALL today (the exemption that would have avoided that is gone). That is
the accepted, safe tradeoff. The bug this commit actually fixes is the
narrower NUL-safety gap above, found during the same investigation.

Adds regression coverage under tests/Infrastructure.Tests/TestTriggerMap/
for: a tab-containing renamed path being correctly attributed instead
of garbled (RenamedFileWithTabInPathIsAttributedCorrectlyNotGarbledByQuoting,
CrossProjectRenameIntoTabContainingPathAttributesTheNewOwner,
RenameIntoTabContainingPathIsAttributedByLayer1InsteadOfUnderSelecting);
truncated -z input failing loudly instead of silently returning a
partial set (ParseNameStatusOutputThrowsOnTruncatedRenameRecord,
ParseNameStatusOutputThrowsOnTruncatedPlainRecord); and renames
correctly forcing ALL with no special-casing
(InPlaceRenameWithoutOwnMapEntryForcesRunAll,
InPlaceRenameToPrefilteredDestinationForcesRunAll,
RenameWhoseOldPathIncidentallyMatchesAnUnrelatedRuleStillAddsBothTargets,
RenameOldPathUnmatchedForcesRunAllLikeAnyOtherLeftover,
RenameWithBothUnmatchedPathsListsBothInUnmatchedFiles,
RenameWhereBothPathsMatchTheirOwnRuleAddsTargetAdditively,
SameCommitRenameWithMapEntryMovedToNewPathForcesRunAll -- the last a
golden-scenario replay of PR microsoft#19486's real diff at head
1e46e48).

docs/ci/test-trigger-map.md and docs/ci/test-trigger-selector-design.md
are updated to describe the rename decision and the shared -z parser.

Refs microsoft#19486

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
Copilot AI review requested due to automatic review settings August 29, 2026 00:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Comment thread tools/SelectTests/Program.cs
Comment thread tools/SelectTests/Program.cs
Comment thread tools/SelectTests/TestSelector.cs Outdated
Comment thread tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs Outdated
Comment thread tools/SelectTests/GraphAffectedProjects.cs
Comment thread tests/Infrastructure.Tests/TestTriggerMap/SelectTestsCliTests.cs Outdated
Fix six issues flagged by automated review on the selector rename-
handling PR (microsoft#19790):

- ParseNameStatusOutput accepted a -z stream missing its final NUL
  terminator, silently parsing a truncated record as valid instead of
  failing loudly. Now throws when the stream is non-empty and does not
  end with a NUL.
- Raw -z-parsed paths (which can contain literal \r/\n/backticks) were
  interpolated unescaped into the job summary and PR comment Markdown.
  Added EscapeForDisplay and applied it at every raw-path interpolation
  site.
- GraphAffectedProjects.ResolveChangedPaths unconditionally rewrote
  '\' to '/' in every changed path, even though git never uses '\' as a
  path separator on any OS -- a literal backslash byte in a filename
  could be corrupted into a fake directory boundary and mis-attributed
  to an unrelated project. Removed the rewrite; Layer 1 now trusts
  git's raw '/'-separated paths exactly like Layer 2 already does.
- Two stale XML-doc/comment blocks describing the old tab-delimited
  "R###\told\tnew" format instead of the current -z NUL-delimited
  "R###\0old\0new\0" format.
- A comment claiming a temp trigger map was "left exactly as checked
  out on disk (still referencing the OLD names)" when the code
  actually rewrites it to the NEW names to simulate the map update
  landing alongside the rename.

Added regression tests:
- ParseNameStatusOutputThrowsOnMissingFinalTerminator
- BackslashContainingRootFileIsNotAttributedToSimilarlyNamedProject
  (a repo-root file literally named `Other\data.txt` was previously
  corrupted to `Other/data.txt` and spuriously attributed to the
  "Other" project via directory containment)

Both were verified to fail against the pre-fix code and pass with it.
169/169 tests pass in tests/Infrastructure.Tests/TestTriggerMap/.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
Copilot AI review requested due to automatic review settings August 29, 2026 01:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs:466

  • This regression test depends on #19486 not having merged: that PR's head already contains only the plural filenames, so once it lands these assertions for the old names fail before the scenario runs. Make the fixture durable by applying the replacements first and asserting that the resulting map contains the new names (or use a self-contained map), so it works whether the checked-out production map is pre- or post-#19486; update the accompanying “checked out on disk today” wording as well.
        var realMapText = File.ReadAllText(Path.Combine(RepoRoot.Path, "eng", "github-ci", "test-trigger-map.yml"));
        Assert.Contains(oldCommonPath, realMapText);
        Assert.Contains(oldUpdatePath, realMapText);
        Assert.Contains(oldVerifyPath, realMapText);

tools/SelectTests/Program.cs:1058

  • The new display escaping still misses the fail-safe reason: TestSelector embeds the unmatched raw filename in EscalationReason, and WriteSelectionComment/WriteSummary emit that value verbatim (lines 835 and 1290). An unmatched path containing a newline or Markdown/HTML markup can therefore still spoof both audit surfaces. Escape that reason at both Markdown sinks (including HTML-sensitive characters because it is prose, not a code span) and cover an unmatched newline-containing path.
    // Escapes bytes a `-z`-parsed path (see ParseNameStatusOutput) can legally contain but that would
    // otherwise corrupt the Markdown this method renders into: `-z` intentionally preserves a path's raw
    // bytes -- including a literal newline, carriage return, or backtick -- that git's older text-mode
    // format would have quoted away. Left raw, such a path could inject extra lines/headings into the
    // step summary or PR comment, or break out of its enclosing `code span`. Escaping is display-only;
    // callers still match/attribute the unescaped path everywhere else.
    private static string EscapeForDisplay(string value)

// summary explains HOW the change reached the test, not just THAT it did.
CauseKind.Layer1Graph => cause.Path is { Count: > 0 } path
? $"graph closure: {string.Join(" → ", path)}"
? $"graph closure: {string.Join(" → ", path.Select(EscapeForDisplay))}"

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants