Prevent duplicate memory leak workflow fixes - #36664
Conversation
Skip leak candidates that already have an equivalent generated fix merged to main or inflight/current, in both the hunter and fixer workflows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9536b655-3ba9-4983-84af-b8642b32e066
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36664Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36664" |
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR updates the Daily Memory Leak Hunter and Memory Leak Fixer agentic workflow prompts to prevent duplicate [leak-scan]/[leak-fix] work by de-duping not only against open items, but also against equivalent fixes already merged into main or inflight/current. It also regenerates the corresponding gh-aw lock files and expands GitHub tool/permission scope needed to query PR metadata.
Changes:
- Add merged-fix de-dup gates based on canonical
Type.Memberextraction and live PRbaseRefName(main/inflight). - Update Daily Leak Hunter GitHub toolsets and workflow permissions to allow PR reads.
- Regenerate gh-aw lock files to reflect the prompt and permission/toolset changes.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| .github/workflows/leak-fixer.md | Adds merged/open fix de-dup logic and canonical API extraction guidance to avoid duplicate fixer PRs. |
| .github/workflows/leak-fixer.lock.yml | Regenerated lock file to reflect updated leak-fixer prompt content hash. |
| .github/workflows/daily-leak-hunter.md | Adds merged-fix de-dup step and updates required GitHub toolsets/permissions in the prompt. |
| .github/workflows/daily-leak-hunter.lock.yml | Regenerated lock file + reflects new permissions/toolsets for MCP/GitHub reads. |
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
Finding
gh pr list errors because the pipeline has no pipefail or explicit fetch validation. jq can return success on empty input, causing the workflow to treat the merged-fix set as empty and create duplicate work. Consensus: 3/3 reviewers after dispute. See the inline comment for the concrete fix.
What looks right
The supported-branch filter uses live baseRefName, canonicalizes both sides consistently, and the generated lock changes faithfully mirror the source permission/toolset changes.
Test coverage
The workflow compilation coverage does not exercise upstream GitHub API/auth/rate-limit failures, which is the remaining behavioral gap.
Prior review status
One existing review raised empty/multiline-title concerns. Those were independently checked and did not produce the claimed false matches, so they were not duplicated here.
Methodology: 3 independent reviewers with adversarial consensus.
…osed merged fetch
Addresses three review findings on the memory-leak dedup workflows:
- daily-leak-hunter.md / leak-fixer.md: collapse embedded CR/LF in issue
titles (`jq ... | gsub("[\r\n]+";" ")`) before the awk Type.Member
extraction, so a multi-line title can't split into two physical lines and
emit a spurious second rooting-API key that over-suppresses an unrelated
candidate.
- leak-fixer.md: guard the same-API open-PR scan on a non-empty $API. An
empty $API made $API_RE empty, collapsing the test() regex to
"^\[leak-fix\] +Fix +([. ]|$)" which false-matches unrelated PRs (e.g.
"[leak-fix] Fix .NET ...") and wrongly skips the fix.
- daily-leak-hunter.md / leak-fixer.md: make the merged-fix fetch
fail-closed. `gh pr list` errors write an empty pipe, and jq still emits []
with exit 0, so a transient API/auth/rate-limit failure would empty the
merged-fix set and re-file/re-create a duplicate. Split fetch from filter
and abort on non-zero fetch status.
Lock files recompiled via `gh aw compile` (body_hash only).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen addressed all three review findings on the leak-dedup workflows (pushed
Each failure mode was reproduced empirically before fixing. Ready for re-review — thanks!
|
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial re-review
The head commit is unchanged, so no new inline comments were added.
gh pr list | jq pipelines can still treat an upstream API/auth/rate-limit failure as an empty dedup result because they lack pipefail or explicit fetch validation. This remains independently confirmed by 3/3 reviewers; the existing inline thread contains the concrete fix.
No additional findings survived adversarial validation. The proposed late-snapshot race and malformed-title fallback regressions were each rejected 2/3 after dispute.
Test coverage: workflow compilation validates schema and lock parity but does not exercise the failed-fetch path.
Prior review status: the author acknowledged and reproduced the fail-open behavior, but no fixing commit has been pushed.
Methodology: 3 independent reviewers with adversarial consensus.
Follow-up to fdf16ef addressing Copilot's re-review: the remaining `gh pr list | jq` dedup pipelines were still fail-open (a failed fetch lets jq succeed on empty input and emit [], so the gate reads as "nothing found" and re-files/re-attempts a duplicate). leak-fixer.md — split fetch from filter and abort on non-zero fetch for: - (b) open [leak-fix] PR already addressing THIS issue, - (c) open [leak-fix] PR fixing the SAME rooting API, - (d) closed-unmerged attempt-cap query (a fail-open here would reset the attempt cap to 0 and re-attempt past the limit). daily-leak-hunter.md — make the `gh issue list` open-scanner-issues fetch fail-closed too, so a transient error can't empty already-filed-apis and re-file duplicate scanner issues. Selection/work-list fetches (own-open-PRs, candidate issue list) are left as-is: a transient failure there yields an empty work list (a no-op), which is safe and cannot create a duplicate. Lock files recompiled via `gh aw compile` (body_hash only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen re: the adversarial re-review ( Copilot's fresh re-review of
So all six de-dup/cap gates across both workflows are now fail-closed. Selection/work-list fetches (own-open-PRs, candidate issue list) are intentionally left fail-open — a transient failure there is a safe no-op, not a duplicate. Both
|
The fail-closed rewrite writes intermediate de-dup state to /tmp/gh-aw/agent/*.json, but the gh-aw runtime pre-creates only /tmp/gh-aw and /tmp/gh-aw/safeoutputs — not /tmp/gh-aw/agent. Without the directory the first redirect fails and the run aborts before any de-dup logic executes. Add `mkdir -p /tmp/gh-aw/agent` at the start of the Step 2 bash block (before the first redirect); /tmp persists across the later fresh-subshell bash calls, so one mkdir covers every write in the job. leak-fixer.md already does this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
Findings
- ❌ Logic — The final mutation gate accepts a new PR title without the required
[leak-fix]prefix, allowing it to become invisible to every later de-dup scan after merge. 3/3 reviewers after dispute. ⚠️ Regression — The gate's global exactly-oneRefs:constraint conflicts with the workflow's required API-match citation path and can reject a legitimate different-mechanism fix. 2/3 reviewers, including the repo domain specialist.
Test coverage
The added Pester coverage does not exercise an untagged emitted PR title or a valid canonical reference plus an additional API-match citation.
Prior review status
No completed COMMENT review containing the Adversarial review and independent reviewer markers exists for this head.
Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/scripts/Assert-LeakFixSafeOutputGate.ps1:50
Invoke-GhJsonmerges stderr into stdout (2>&1) before JSON parsing. Ifghever writes a warning/notice to stderr alongside JSON (even when exit code is 0), the combined stream will no longer be valid JSON and this final gate will fail unexpectedly. Capture stdout and stderr separately so success-path JSON parsing only sees stdout, while failure-path errors still include stderr for diagnostics.
$output = & gh @Arguments 2>&1
if ($LASTEXITCODE -ne 0) {
throw "'gh $($Arguments -join ' ')' failed with exit code $LASTEXITCODE`: $output"
}
$raw = ($output -join [Environment]::NewLine)
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
Findings
❌ Logic — The trusted gate permits a same-API exception based on an agent-authored state entry without requiring the generated PR to disclose the conflicting PR and mechanism comparison. The inline comment has the concrete failure path. Consensus: repo domain specialist + 2 reviewers after dispute.
❌ Regression — unresolved prior feedback — daily-leak-hunter snapshots merged fixes in pre-agent-steps, but can run for up to 90 minutes before Process Safe Outputs creates issues. A fix merged during that interval remains invisible and permits a duplicate [leak-scan] issue. The existing review feedback already identifies this mutation-boundary gap, so no duplicate inline comment was added.
What looks right
leak-fixer restores trusted scripts and executes its final gate before Process Safe Outputs; that is the appropriate mutation-boundary pattern.
Test coverage
The Pester additions cover direct matches, revert parity, and several gate success/failure paths, but do not require an emitted same-API exception citation or cover a final live refresh for the hunter.
Prior review status
Earlier feedback on the additional Refs: citation is addressed at this head: the gate now counts only the required target-issue reference. The stale hunter snapshot remains unresolved.
Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist. COMMENT-only review.
Require structured same-API comparison disclosures and add a trusted final live de-dup gate for leak-hunter issue creation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen both findings from review 4964158437 are addressed in AI-assisted response. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
.github/scripts/Assert-LeakFixSafeOutputGate.ps1:41
- Invoke-GhJson captures
ghstderr via2>&1and then parses the combined output as JSON. In PowerShell this can include stderr asErrorRecordobjects even whenghexits 0 (e.g., warnings/deprecation notices), which will corrupt$rawand causeConvertFrom-Jsonto fail (false fail-closed). The repo already avoids this by splitting stdout/stderr before parsing (see.github/scripts/Query-CiFixPRs.ps1:142-158). Please adopt the same stdout/stderr separation here (and in the matching hunter gate) so successful JSON responses remain parseable even ifghemits warnings.
$output = & gh @Arguments 2>&1
if ($LASTEXITCODE -ne 0) {
throw "'gh $($Arguments -join ' ')' failed with exit code $LASTEXITCODE`: $output"
}
$raw = ($output -join [Environment]::NewLine)
.github/scripts/Assert-LeakHunterSafeOutputGate.ps1:40
- Invoke-GhJson captures
ghstderr via2>&1and then parses the combined output as JSON. Ifghemits any warning/progress text to stderr while still exiting 0,$rawcan includeErrorRecordtext andConvertFrom-Jsonwill fail, blocking issue creation unnecessarily. The repo already handles this by separating stdout and stderr before parsing (e.g.,.github/scripts/Query-CiFixPRs.ps1:142-158). Please apply that pattern here (and in the leak-fix gate) so benign stderr doesn’t break JSON parsing.
$output = & gh @Arguments 2>&1
if ($LASTEXITCODE -ne 0) {
throw "'gh $($Arguments -join ' ')' failed with exit code $LASTEXITCODE`: $output"
}
$raw = ($output -join [Environment]::NewLine)
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
❌ Config Impact — The Daily Memory Leak Hunter’s new final gate executes with a generated safe_outputs job that lacks pull-requests: read, so its required gh pr list calls fail whenever a real create_issue output is emitted.
Type.Member cannot reach the fixer’s mechanism-aware exception.
main/inflight/current fix as reverted and cause unnecessary re-evaluation.
gh, leaving the compiled safe-output permission boundary untested.
What looks right: the trusted default-branch gate checkout and fail-closed handling of unavailable or truncated de-dup metadata are correctly wired through the source and generated workflow.
Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.
Consensus: permission gap 3/3 + specialist; mechanism mismatch 2/3 after dispute; branch-scoped revert gap 3/3 after dispute; permission-boundary test gap 1/3 + specialist.
Test coverage: decision logic is well covered, but the compiled job-permission contract for the new gate is not.
Prior review status: no qualifying adversarial review exists for this exact head; existing inline and issue comments were considered.
Grant the hunter gate its read scope, support structured mechanism overrides, scope revert parity by branch, and separate gh stdout from diagnostics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen all four findings from review 4964976486 are addressed in AI-assisted response. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
.github/scripts/Assert-LeakHunterSafeOutputGate.ps1:10
- Pin $PSNativeCommandUseErrorActionPreference to $false in this gate script for deterministic
ghinvocation behavior under$ErrorActionPreference = 'Stop'. The repo does this in othergh ... 2>&1wrappers (e.g..github/scripts/Query-CiFixPRs.ps1:34-40) to ensure native-command stderr/nonzero exits don’t surface as terminating errors that bypass structured error handling.
$ErrorActionPreference = 'Stop'
Import-Module (Join-Path $PSScriptRoot 'LeakWorkflowDedup.psm1') -Force
.github/scripts/Assert-LeakFixSafeOutputGate.ps1:11
- Pin $PSNativeCommandUseErrorActionPreference to $false in this gate script. Other repo scripts that wrap
& gh ... 2>&1do this explicitly (e.g..github/scripts/Query-CiFixPRs.ps1:34-40) to prevent native-command stderr/nonzero exits from becoming terminating errors under$ErrorActionPreference = 'Stop'on future runner images. Without pinning, a change in the default preference can bypassInvoke-LeakGhJson’s exit-code handling and break the safe-output gate unpredictably.
$ErrorActionPreference = 'Stop'
Import-Module (Join-Path $PSScriptRoot 'LeakWorkflowDedup.psm1') -Force
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
Addressed the latest Copilot review in
Ready for re-review. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/scripts/LeakWorkflowDedup.Tests.ps1:36
- This test can pass even if the expected strings are missing:
IndexOf(...)returns-1, and-1will still satisfyShould -BeLessThan ...depending on the other index. Add explicit assertions that both substrings were found before comparing their order, so the test fails when the helper body changes unexpectedly.
$helper.IndexOf('$PSNativeCommandUseErrorActionPreference = $false') |
Should -BeLessThan $helper.IndexOf('$output = & gh @Arguments 2>&1')
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
Finding
❌ Logic — The resolver treats two independent, still-active reverts of the same fix as if they restore it. With fix #100 and siblings #200 and #201 that both contain Reverts dotnet/maui#100, both direct reverters remain active, but line 282 computes an even count as active. Neither sibling reverts the other, so the original fix is still absent; the hunter and fixer continue treating #100 as proof and suppress a needed re-fix. The recursion already handles a real revert-of-revert, whose PR targets the first revert. Treat a target as active only when it has zero active direct reverters, and update the sibling-revert test that currently encodes the parity result. Consensus: repo domain specialist + 2 independent reviewers after dispute.
Test coverage
The added sibling-revert test enshrines the incorrect even-parity outcome; it should instead distinguish independent siblings from a true revert-of-revert chain.
Prior review status
Existing feedback was considered; no qualifying adversarial review existed for this exact head.
Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist. COMMENT-only review.
| } | ||
|
|
||
| [void]$visiting.Remove($PullRequestNumber) | ||
| $active = ($activeRevertCount % 2) -eq 0 |
There was a problem hiding this comment.
❌ Logic — Two independent active reverts of the same fix make this even-parity check report the fix as active, although neither revert restores it. The hunter/fixer then suppress a needed re-fix. A real revert-of-revert targets the first revert and is already handled by the recursion; only zero active direct reverters should keep the target active.
Flagged by: repo domain specialist + 2 independent reviewers after dispute
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
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 from this PR and let us know in a comment if this change resolves your issue. Thank you!
Summary
[leak-scan]issue when the canonicalType.Memberalready has an exact[leak-fix]PR merged tomainorinflight/current.Type.Memberby persisting explicit decisions and rechecking every live API match before emission; the semantic comparison remains agent-authored and reviewer-visible.This prevents repeats such as #36659 after #36369 had already merged the
ListView.RefreshCommandfix intoinflight/current, while still allowing distinct retention mechanisms such as the twoGradientBrush.GradientStopsleaks to be handled independently.Validation
.github/aw/actions-lock.json:v0.85.4pwsh -NoLogo -NoProfile -Command Invoke-Pester...— 20 passedgh aw compile daily-leak-hunter --strict— 0 warningsgh aw compile leak-fixer --strict— 0 warningsgh aw compile --validate— all 14 workflows compiled; one pre-existing warning incopilot-evaluate-tests.mdgit diff --check