You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Never classify a CLOSED issue as open-on-main in release-readiness (dotnet#36410)
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
## Why
The release-readiness regression classifier could report a **CLOSED**
issue as an active `open-on-main` regression (Tier 2 — "wait for main
merge, then backport"). That's contradictory: an unmerged OPEN PR cannot
have closed a completed issue. It happens when a giant still-open
"Candidate" changelog PR `Fixes`-lists dozens of issues — its OPEN state
gets attributed to an already-completed issue. Real-world: issue dotnet#35615
(CLOSED/COMPLETED) surfaced under `open-on-main` in the SR9 tracker
while candidate PR dotnet#35716 was still open.
## What
`Classify-RegressionCandidate` now enforces: **never emit `open-on-main`
for a CLOSED issue.** When the aggregated best verdict is exactly
`open-on-main` and the issue is CLOSED:
1. First try the same comment-prose recovery the empty-candidate path
uses — a merged fix verifiably present on the SR wins →
`closed-fix-unlinked` (Tier 3, "no ship risk; add a closing reference
for traceability").
2. Otherwise fall to the honest `no-fix-yet` (Tier 3 for a CLOSED issue
via the existing `Get-OverallVerdict` downgrade) — the automation can't
pin a verified fix on this SR and the open candidate hasn't merged.
The shared recovery logic is extracted into a new
`Resolve-ClosedFixUnlinked` helper, called from both the
empty-`strongPrs` CLOSED path and the new guard, preserving the
fix-phrase gate, merged-on-SR gate, revert guards, and tooling-only
skips exactly.
**Strictly scoped:** only `open-on-main` + CLOSED is contradictory.
Every other verdict (`merged-on-main-no-backport`,
`backport-in-progress`, `rejected-from-sr`, `in-sr-*`,
`needs-human-review`) is unchanged even for CLOSED issues — those remain
legitimately actionable (the SR may still need the backport).
Genuinely-OPEN issues still get `open-on-main`.
## Tests
Three new unit tests in `Test-ReleaseReadiness.ps1`:
1. CLOSED issue + OPEN candidate on main + no comment fix →
`no-fix-yet`, proven non-blocking (🟢) via `Get-OverallVerdict`.
2. CLOSED issue + OPEN candidate + comment-cited merged fix on SR →
`closed-fix-unlinked` (recovery wins).
3. Regression guard: OPEN issue + OPEN candidate → stays `open-on-main`.
Full suite green (829/0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: PureWeen <223556219+Copilot@users.noreply.github.com>
evidence=@("Issue is CLOSED and fix PR $prList is MERGED and present on $($Ctx.srBranch), but was never linked to the issue (no closing keyword, no timeline cross-reference). Linkage recovered from a closing comment that explicitly names the fix.")
recommendedAction="No ship risk — fix is already in the SR. Add a closing reference for traceability (e.g. ``Fixes #$($Issue.number)`` in $prList, or link via the issue's Development panel) so future runs classify it automatically."
2118
+
}
2119
+
}
2120
+
return$null
2121
+
}
2122
+
2036
2123
functionClassify-RegressionCandidate {
2037
2124
param($Issue,$CandidatePrs,$Ctx,$SrContents)
2038
2125
@@ -2171,74 +2258,10 @@ function Classify-RegressionCandidate {
2171
2258
2172
2259
# ── FALLBACK: closed issue whose fix lives ONLY in comment prose ──
2173
2260
# No timeline-cross-referenced candidate survived the evidence filter,
2174
-
# but the issue is CLOSED. Maintainers routinely close a regression with
2175
-
# a plain-text comment ("fixed by PR #35028") that GitHub never turns
2176
-
# into a structured link. Recover the cited PR and — ONLY when it
2177
-
# actually MERGED and its commit is verifiably on THIS SR branch —
2178
-
# classify 'closed-fix-unlinked': the fix is present (no ship risk), but
2179
-
# the issue<->PR link is missing and should be added for traceability.
2180
-
#
2181
-
# Two gates make prose evidence safe, and BOTH are required:
2182
-
# 1. fix-phrase ONLY — the comment must pair the PR with fix/resolve/
2183
-
# close language ("was fixed by PR #X"). A BARE mention is rejected
2184
-
# because regression issues routinely name the CAUSE PR for context
2185
-
# ("Before PR #32080 ... After PR #32080 the behavior changed"), and
2186
-
# the cause naturally lives on the branch — it is not a fix.
2187
-
# 2. merged AND on the SR branch — a cited PR that never merged, or
2188
-
# merged elsewhere, is not proof the fix shipped here.
evidence=@("Issue is CLOSED and fix PR $prList is MERGED and present on $($Ctx.srBranch), but was never linked to the issue (no closing keyword, no timeline cross-reference). Linkage recovered from a closing comment that explicitly names the fix.")
recommendedAction="No ship risk — fix is already in the SR. Add a closing reference for traceability (e.g. ``Fixes #$($Issue.number)`` in $prList, or link via the issue's Development panel) so future runs classify it automatically."
2239
-
}
2240
-
}
2241
-
}
2261
+
# but the issue is CLOSED. Recover a fix cited only in a closing comment
2262
+
# (see Resolve-ClosedFixUnlinked for the fix-phrase + merged-on-SR gates).
evidence=@("Issue is CLOSED but the candidate fix PR (#$($best.pr.number)) is OPEN/unmerged on $($best.pr.baseRef) — an unmerged PR cannot have closed this issue; the real fix likely shipped elsewhere or the candidate is stale. Not an active SR regression.")
2385
+
candidateFixPrs=@($strongPrs|ForEach-Object { @{
2386
+
number=$_.number; title=$_.title; state=$_.state
2387
+
baseRef=$_.baseRef; evidenceType=$_.evidenceType
2388
+
onMain=$_.onMain; backports=$_.backports
2389
+
} })
2390
+
recommendedAction='Verify the fix is present on this SR (or add a closing reference); the open candidate PR has not merged.'
2391
+
}
2392
+
}
2393
+
2338
2394
$recAction=switch ($best.verdict) {
2339
2395
'in-sr-active' { 'No action — fix is shipping' }
2340
2396
'in-sr-reverted' { 'Investigate: backport landed and was reverted on SR' }
0 commit comments