Fix rerun review scanner: dispatch review-trigger.yml instead of reimplementing AzDO - #36080
Conversation
…menting AzDO The rerun-review-scanner's safe-output job never dispatched a single review: in the gh-aw safe-output job context the `gh` CLI returns a spurious HTTP 404 for `repos/.../pulls/N` (even with pull-requests:write on a public repo), so the PR-fetch guard skipped every decision — including open PRs the AI chose to `trigger`. PR #35955 fixed decision batching, but the dispatch stayed dead. Make the scanner use the same path as a maintainer `/review`: instead of re-implementing PR validation + OIDC + the AzDO trigger inside the safe job, it now dispatches the same `review-trigger.yml` workflow `/review` runs, via `workflow_dispatch`. That workflow owns PR validation, the `s/agent-review-in-progress` lock, platform inference, OIDC, and the AzDO pipeline trigger. - Invoke-RerunReviewTrigger.ps1 is now pure: it validates batched decisions against candidates.json and emits an actions JSON. No gh/AzDO/OIDC/lock/ rate-limit I/O (candidates.json already carries head SHA, platform, and rerun comment id from the pre-activation job, whose gh calls work). - A github-script (octokit) step performs all GitHub writes — octokit works in this safe-job context where the gh CLI does not (the built-in safe_outputs job uses it successfully). trigger -> createWorkflowDispatch + 1; skip -> -1 reaction + remove the queue label. - Permissions: +actions:write (for the dispatch), -id-token (OIDC moved to review-trigger.yml); drop the AZDO_TRIGGER_* secrets from the job. - Drop the scanner's bespoke rate limit: review-trigger.yml's per-PR lock plus queue-label consumption bound the trigger rate the same way /review is. - Tests rewritten around the pure validation/emit surface (36 Pester tests). Recompiled the gh-aw lock file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36080Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36080" |
…r_association The rerun candidate builder decided whose `/review -b <branch> -p <platform>` command to trust using the comment's `author_association`. That field is viewer-dependent: under the Actions GITHUB_TOKEN, a maintainer whose org membership is private reads as CONTRIBUTOR (not MEMBER), so their command was treated as untrusted, the `-b`/`-p` options were dropped, and the rerun fell back to the `main` pipeline branch. This is exactly why a live non-dry scan dispatched four net11 PRs (feature/enhanced-reviewer) on `main` instead. Fix it the same way `/review` itself authorizes — a live collaborator-permission lookup (`repos/<owner>/<repo>/collaborators/<user>/permission` -> write/maintain/ admin). That endpoint only needs metadata:read (every GITHUB_TOKEN has it), is the exact call review-trigger.yml's "Check actor permission" step makes, and reflects the user's current access rather than a per-comment snapshot. No new secret or permission is required. - Resolve-RerunEligibility.ps1: add Test-ReviewOptionLoginTrusted (cached per login) and have Get-LatestReviewCommandOptions compute trust through it. Removed the author_association gate and the -AllowedAuthorLogins plumbing. - Query-RerunReadyPRs.ps1: drop Test-UserCanSetReviewOptions / Get-ReviewOptionAuthorLogins; call Get-LatestReviewCommandOptions with -Owner/-Repo. - Tests rewritten around per-login permission trust (33 pass), incl. a regression test that a command with author_association=NONE is still honored when the login has write access. Verified against real data: PR #34564's history now resolves to pipelineRef=feature/enhanced-reviewer (author kubaflo, author_association CONTRIBUTOR) instead of main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
Multi-model adversarial review — PR #36080 (round 2, commit
|
| Round-1 finding | Status | Verification |
|---|---|---|
| ❌ Batch poisoning (one bad decision drops the whole batch) | ✅ Fixed | if: ${{ !cancelled() }} on the dispatch step runs it even when validate exit 1s; actions.json is written before the exit so valid actions persist; the JS no-ops when the file is absent and core.setFaileds on a torn/parse error; the job still goes red so a dropped decision stays visible. .lock.yml faithfully mirrors the .md. |
| ✅ Resolved (documented, intentional) | The new "Rerun volume" subsection matches the code: the deterministic new-activity gate in Resolve-RerunEligibility.ps1 means identical PR state can't be re-queued, and re-entry needs a human /review rerun each cycle. Confirmed the trade-off is sound and matches manual /review. |
|
💡 Transient permission blip → silent main downgrade |
✅ Fixed | Test-ReviewOptionLoginTrusted now distinguishes a definitive HTTP 404/410 (cached untrusted) from a transient error (retried 3×, never cached), so a momentary blip can't downgrade a maintainer's custom branch. |
💡 $Login path interpolation |
✅ Hardened | ^[A-Za-z0-9][A-Za-z0-9-]*$ guard rejects bot/invalid logins before the API call; confirmed it rejects no valid GitHub login. |
Tests: 75/75 Pester pass (one reviewer ran the suite). The new permission tests are isolated (cache reset in BeforeEach, Get-CollaboratorPermissionResult mocked) and cover trust / no-trust / 404-cache / transient-no-cache / recovery.
New finding this round
❌ Skip-path TOCTOU — skip removes the queue label against stale state · .github/workflows/rerun-review-scanner.md:209
3/3 reviewers agree it's a real silent-drop race (corroborating MauiBot's inline ❌); 2 rate it ❌ must-fix, 1 rates it
The validate step's expected_head_sha == candidate.headSha is an anti-hallucination check against the scan-time candidates.json, not a live-head check. So the skip branch can strip s/agent-ready-for-rerun from a PR whose head has already advanced.
- Trigger: scan snapshots PR #X at head
abc(label present) → minutes later the author pushesdef(label still present, or a fresh/review rerunkeeps it) → the agent, blind todef, returnsskip→ this step validates (abc == abc) and removes the label from thedefhead. The newly-queued rerun fordefis dropped; the 👎 lands on the old rerun comment. - Asymmetry: the trigger path is immune — it doesn't remove the label; it dispatches
review-trigger.yml, which re-validates and reviews the live head. Onlyskipdoes a destructive, non-revalidated removal. - Severity nuance: it's recoverable — one more human
/review rerunre-applies the label and the next scan re-evaluates — and the window is narrow (needsskip+ concurrent new author activity within the scan→agent→dispatch minutes). That's why one reviewer holds it at⚠️ . Still a genuine silent-drop of a user's request, which is why the majority (and MauiBot) call it ❌. - Fix (verified feasible): the action object already carries
headSha, and this step uses octokit (not theghCLI the PR says 404s here), so a live read works. Before theskipreaction/removal,github.rest.pulls.get(...)and onlyremoveReadyLabelwhenlivePr.head.sha === a.headShaand the ready label is still present; otherwise log and leave the label. Scope the guard toskiponly. (Inline comment added at:209.)
💡 Minor (single-reviewer, non-blocking)
actions.jsonnot written atomically (Invoke-RerunReviewTrigger.ps1:257).Set-Contentwrites directly rather than temp-file + rename. Flagged⚠️ by one reviewer; another explicitly judged it acceptable because a torn/partial write failsJSON.parse→core.setFailed(loud failure, never silently consumed) andrunner.tempis per-run so no stale file can be read. Net: not data loss; an atomic temp+rename would be belt-and-suspenders. (1/3)- Treat
403/401as definitive-untrusted inTest-ReviewOptionLoginTrusted. Today a persistent403falls into the transient branch → 3 pointless retries (~6s) before the (correct) untrusted result. Classifying401/403alongside404/410would skip the wasted retries. Not expected on this public repo. (1/3)
Prior reviews: MauiBot posted the skip-path ❌ (which all 3 reviewers independently corroborate here) plus an AI Summary; the round-1 adversarial review's findings are resolved by 6f8aa93. No unresolved prior ❌ other than the skip-path race addressed above.
Bottom line: the round-1 fixes are solid and the architecture is sound. The one outstanding item is the skip-path label-removal race — worth guarding with a live head-SHA check (cheap, feasible, skip-only) before merge.
Methodology: 3 independent reviewers with adversarial consensus; severity split and the proposed fix's feasibility (headSha present in actions.json) verified against source rather than asserted.
… race The `skip` branch removed `s/agent-ready-for-rerun` based only on the validator's anti-hallucination check (`expected_head_sha == candidate.headSha`), which compares against the SCAN-TIME snapshot, not the live PR head. So if an author pushed a new head (or a fresh `/review rerun`) between the scan and the agent's decision, a stale `skip` stripped the queue label from the new head and 👎'd the superseded comment — silently dropping a legitimately-queued rerun. (MauiBot + all 3 round-2 reviewers flagged this; the `trigger` path is immune because it never removes the label — review-trigger.yml re-validates the live head.) Guard the skip path with a live head read (octokit `pulls.get` — the gh CLI is what 404s in this job, octokit works). Only react `-1` + remove the label when the live head still equals the scan-time `headSha`. If the head advanced, leave the label for the next scan to re-evaluate and don't react. If the live read fails, conservatively leave the label. Scoped to `skip` only; `trigger` is unchanged. Recompiled the lock; verified all branches via a node simulation (head-match → react+remove, head-advanced → leave, read-fail → leave, trigger → unchanged). Round-1 minors left as-is with rationale: actions.json temp+rename is unnecessary (a torn write fails JSON.parse -> core.setFailed, and runner.temp is per-run); 401/403 are NOT reclassified as definitive-untrusted because a 403 can be a transient rate limit that should be retried, not treated as a downgrade. 75 Pester tests still pass (JS-only change). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thanks for round 2 — all round-1 items confirmed resolved, and the new finding is addressed in ❌ Skip-path TOCTOU (label removed against stale head) — ✓ Fixed. The
Scoped to 💡 💡 Classify Tests: 75/75 Pester pass (this round is a JS-only change in the dispatch step). Re-triggering the automated review next. |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
| inputs: { | ||
| pr_number: String(prNumber), | ||
| platform: a.platform || '', | ||
| pipeline_ref: a.pipelineRef || 'main', |
There was a problem hiding this comment.
[major] Security / Rerun Freshness — The scanner validates the agent decision against the scan-time headSha, but the dispatched review-trigger.yml receives only pr_number, platform, and pipeline_ref. If the PR gets a new push after this validation but before the downstream workflow starts, review-trigger.yml only checks that the PR is open and then triggers review for the new, unvalidated head. Please pass the expected head SHA (and have review-trigger.yml reject/no-op stale dispatches) at the final trusted boundary.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@kubaflo — new AI review results are available based on this last commit:
e075ae1. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ⚠️ INCONCLUSIVE
Platform: ANDROID
⚠️ verify-tests-fail.ps1exited before writing a verification report. Diagnostics below.
Exit code: 3
Likely cause:
- Test detection failed — no runnable tests were found in the PR diff.
- No fix files detected in the diff (PR may be test-only — should now run in failure-only mode).
Gate output log (last 60 lines)
📁 Output directory: CustomAgentLogsTmp/PRState/36080/PRAgent/gate/verify-tests-fail
🔍 Detecting base branch and merge point...
No PR detected, scanning remote branches for closest base...
✅ Base branch: main (via closest-merge-base)
✅ Merge base commit: 19abc43e
(1 commits ahead of main)
╔═══════════════════════════════════════════════════════════╗
║ VERIFY FAILURE ONLY MODE ║
╠═══════════════════════════════════════════════════════════╣
║ No fix files detected - will only verify: ║
║ 1. Tests FAIL (proving they catch the bug) ║
║ ║
║ Use this mode when creating tests before writing a fix. ║
╚═══════════════════════════════════════════════════════════╝
🔍 Auto-detecting test filter from changed test files...
⚠️ No tests detected in this PR.
Searched for: UI tests, unit tests, XAML tests, device tests
Consider adding tests via write-tests-agent.
📋 Pre-Flight — Context & Validation
Issue: Unknown - no linked issue available from local context
PR: #36080 - Rerun review scanner refactor (local squashed PR branch)
Platforms Affected: CI/review infrastructure; requested test platform: android
Files Changed: 6 implementation/workflow/docs, 2 test
Key Findings
- GitHub metadata could not be fetched because
ghis unauthenticated; local squashed PR commit and working repository context were used. - The PR changes rerun scanner automation under
.github/scripts/**and.github/workflows/rerun-review-scanner.*, including batched decisions, deterministic candidate validation, and dispatchingreview-trigger.yml. - Expert review found a trigger-path TOCTOU gap: trigger decisions are validated against scan-time head SHA before dispatch, but
review-trigger.ymldoes not receive or validate that expected SHA before lock/OIDC/AzDO work. - The pre-run gate was explicitly inconclusive and was not rerun.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: medium
Errors: 1 | Warnings: 2 | Suggestions: 0
Key code review findings:
- ❌ Trigger path lacks end-to-end head-SHA freshness at the final trusted boundary (
rerun-review-scanner.mddispatches only PR/platform/ref;review-trigger.ymlvalidates only open state). ⚠️ Scanner reacts+1after workflow dispatch, before the downstream trigger actually succeeds.⚠️ review-trigger.ymlremovess/agent-ready-for-rerunbefore OIDC/AzDO success and does not restore it on trigger failure.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #36080 | Validate batched scanner decisions against deterministic candidates, emit action file, dispatch review-trigger.yml for triggers, handle skips in safe-output job |
.github/scripts/Invoke-RerunReviewTrigger.ps1, tests, scanner workflow/docs |
Original PR; directionally good but trigger freshness is incomplete |
🔬 Code Review — Deep Analysis
Code Review — PR #36080
Independent Assessment
What this changes: PR #36080 refactors the rerun-review scanner path so the gh-aw safe-output job validates batched agent decisions against deterministic candidates, emits normalized actions, and dispatches review-trigger.yml rather than directly triggering AzDO from the scanner job.
Inferred motivation: Avoid gh CLI failures in the gh-aw safe-output context, process more than one rerun candidate per scan, and centralize lock/OIDC/AzDO triggering in the existing trusted review trigger workflow.
Reconciliation with PR Narrative
Author claims: GitHub PR metadata could not be fetched because gh is unauthenticated and public web fetch for the PR returned an action-denied page. Local commit/diff context was used.
Agreement/disagreement: The local diff supports the inferred motivation, but the trigger path leaves a stale-head race after scan-time validation.
Prior Review Reconciliation
No prior ❌ Error findings could be fetched; gh is unauthenticated in this environment.
Blast Radius Assessment
- Runs for all instances: Yes — affects scheduled/manual rerun scanner processing for all queued PRs labeled
s/agent-ready-for-rerun. - Startup impact: No application startup impact; CI/review infrastructure only.
- Static/shared state: No runtime static state, but workflow labels and dispatch side effects are persistent shared state.
CI Status
- Required-check result: undetermined
- Classification: undetermined; gate was pre-run and inconclusive by prompt, and
gh pr checkscannot run without authentication. - Action taken: confidence capped; no PR comments posted.
Findings
❌ Error — Trigger path lacks end-to-end head-SHA freshness at the final trusted boundary
The PR validates agent output against the scan-time candidate head SHA before writing dispatch actions, but the safe-output job dispatches review-trigger.yml with only pr_number, platform, and pipeline_ref. If a PR advances after scanner validation but before the dispatched workflow starts, review-trigger.yml validates only that the PR is open and then locks/triggers review for the newer head. This can run privileged/costly review infrastructure on content the scanner did not evaluate.
⚠️ Warning — Trigger acknowledgement is emitted before downstream trigger success
The scanner reacts +1 immediately after successfully dispatching review-trigger.yml. A downstream stale-head/no-op or OIDC/AzDO failure can leave a user-visible acceptance reaction even though no review rerun was actually triggered.
⚠️ Warning — Ready queue label can be removed before downstream trigger success
review-trigger.yml removes s/agent-ready-for-rerun during lock acquisition before OIDC/token/AzDO trigger completion. If downstream triggering fails, cleanup clears the in-progress lock but does not restore the ready label, so a queued rerun can be lost.
Failure-Mode Probing
- PR head changes after scanner validation but before
review-trigger.ymlstarts: current PR can trigger review for the new head without scanner approval. review-trigger.ymldispatch succeeds but AzDO trigger fails: current PR may already have reacted+1and removed the ready label even though no rerun happened.- Skip decision sees a changed head: current PR already handles this conservatively by live-reading the head and leaving the queue label.
Verdict: NEEDS_CHANGES
Confidence: medium
Summary: The refactor is directionally sound, but trigger decisions need the same freshness and transactional semantics as skip decisions. The best alternative found moves trigger acceptance into review-trigger.yml so the final trusted boundary validates the scan-time head, reacts only after AzDO trigger success, and restores the queue label on post-lock trigger failure.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Add expected_head_sha to review-trigger.yml and fail Validate PR on stale head |
3 files | Closes main race but stale dispatch becomes workflow failure; early +1/label-loss remain |
|
| 2 | try-fix | Re-read live PR head in scanner immediately before dispatch | 2 files | Narrows but does not close race before downstream lock/OIDC/AzDO | |
| 3 | try-fix | Add expected_head_sha and make stale dispatch a clean no-op via gated downstream steps |
3 files | Clean stale no-op, but early +1 and ready-label loss remain |
|
| 4 | try-fix | Move trigger acceptance to review-trigger.yml: expected SHA validation, deferred +1, ready-label restore on trigger failure |
✅ Best available (static pass; Pester blocked) | 3 files | Expert-reviewed PASS; demonstrably stronger than PR fix and prior candidates |
| PR | PR #36080 | Validate scanner decisions and dispatch review-trigger.yml for trigger decisions |
8 files | Original PR lacks end-to-end trigger freshness and transactional acknowledgement |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Recommended end-to-end expected_head_sha validation at review-trigger.yml, not only in scanner pre-dispatch code |
| maui-expert-reviewer | 2 | Yes | Refined candidate 1 to no-op stale dispatches instead of failing the workflow |
| maui-expert-reviewer | 3 | Yes | Flagged candidate 3 transaction gaps; recommended moving +1/ready-label ownership into review-trigger.yml |
| maui-expert-reviewer | 4 | No | Candidate 4 passed; only caveats were best-effort reaction, SHA validation, and optional exact-head recheck after lock. SHA validation was added. |
Exhausted: Yes
Selected Fix: Candidate #4 — It is the only candidate that validates scan-time head SHA at the final trusted boundary, avoids false-positive +1 reactions, and preserves the rerun queue on downstream trigger failure. Full test execution remained blocked by missing Pester; the available static checks passed.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current description accurately explains the raw PR refactor, but the winning fix adds downstream stale-head validation and transactional rerun acknowledgement/queue handling that the current metadata does not describe.
Recommended title
Review Scanner: Dispatch review-trigger.yml with stale-head and rerun outcome guards
Recommended description
## Problem
The `/review rerun` scanner (`rerun-review-scanner`) dispatched zero AzDO reviews after #35955, and the few it should dispatch would target the wrong branch. Two independent bugs:
### Bug 1 — every dispatch aborted with a spurious 404
In the gh-aw safe-output job, the `gh` CLI returns `HTTP 404` for `repos/dotnet/maui/pulls/N` — even with `pull-requests: write` on a public repo. #35955 misread these as transient and "hardened" the not-found guard, so it now faithfully confirms the bogus 404 and skips every PR. Evidence it is not a permission/token problem: the `pre_activation` job (only `Metadata: read`) reads the same PRs fine, and the built-in `safe_outputs` job (octokit) succeeds in the same run.
### Bug 2 — custom review branches silently downgraded to `main`
The candidate builder decided whose `/review -b <branch> -p <platform>` to trust using the comment's `author_association`. Under the Actions `GITHUB_TOKEN`, a maintainer whose org membership is private reads as `CONTRIBUTOR` (not `MEMBER`), so their command was dropped and the rerun fell back to the `main` pipeline. A live scan confirmed it dispatched four net11 (`feature/enhanced-reviewer`) PRs on `main`.
## Fix — do exactly what a maintainer `/review` does, with rerun-specific freshness guards
**Bug 1:** instead of re-implementing PR validation + OIDC + the AzDO trigger inside the safe job, the scanner now dispatches the same `review-trigger.yml` workflow `/review` runs, via `workflow_dispatch`. That workflow owns PR validation, the `s/agent-review-in-progress` lock, platform inference, OIDC, and the AzDO trigger. (`workflow_dispatch` via `GITHUB_TOKEN` always creates a run — it is exempt from Actions recursion-prevention.)
- `Invoke-RerunReviewTrigger.ps1` is now pure: validate batched `decisions` against `candidates.json`, emit `actions.json`. No `gh`/AzDO/OIDC/lock/rate-limit I/O.
- A `github-script` (octokit) step performs all GitHub writes — octokit works in the safe-job context where the `gh` CLI does not.
- For `trigger`, the scanner dispatches `review-trigger.yml` with `pr_number`, `platform`, `pipeline_ref`, the scan-time `expected_head_sha`, and the `rerun_comment_id`.
- `review-trigger.yml` validates the expected SHA at the final trusted boundary and cleanly no-ops stale rerun dispatches before lock/OIDC/AzDO work.
- The scanner no longer reacts `+1` immediately after workflow dispatch. `review-trigger.yml` reacts `+1` only after AzDO run creation succeeds.
- If a scanner-dispatched rerun fails after the in-progress lock is applied, cleanup clears the lock and restores `s/agent-ready-for-rerun` so the queued rerun is not lost.
- `skip` decisions remain scanner-owned: the safe-output job reacts `-1` and removes the queue label only after confirming the live head still matches the scan-time candidate.
- Permissions: `+actions:write`, `−id-token:write`; dropped the `AZDO_TRIGGER_*` secrets from the scanner job.
**Bug 2:** authorize `/review` options by a live collaborator-permission lookup (`collaborators/<user>/permission` → write/maintain/admin) — the exact call `review-trigger.yml`'s auth step makes. It only needs `metadata: read` (every token has it) and reflects current access. No new secret or permission.
- `Resolve-RerunEligibility.ps1`: add `Test-ReviewOptionLoginTrusted` (cached per login); `Get-LatestReviewCommandOptions` computes trust through it. Removed the `author_association` gate.
- `Query-RerunReadyPRs.ps1`: drop the `author_association` helpers; pass `-Owner/-Repo`.
## Validation
- 69 Pester tests pass (36 dispatch + 33 resolver), incl. a regression test that an `author_association=NONE` command is still honored when the login has write access.
- Live, real (non-dry) scan: the safe job validated all decisions with no 404s and octokit performed real `createWorkflowDispatch`, producing two `review-trigger.yml` runs that triggered real AzDO `maui-copilot` builds (HTTP 200, Run IDs 14459427 & 14459428).
- Real-data check for Bug 2: PR #34564's history now resolves to `pipelineRef=feature/enhanced-reviewer` (author `kubaflo`, `author_association=CONTRIBUTOR`) instead of `main`, using the live permission lookup.
## Files
- `.github/scripts/Invoke-RerunReviewTrigger.ps1`, `.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1`
- `.github/scripts/Resolve-RerunEligibility.ps1`, `.github/scripts/Resolve-RerunEligibility.Tests.ps1`
- `.github/scripts/Query-RerunReadyPRs.ps1`
- `.github/workflows/rerun-review-scanner.md`, `.github/workflows/rerun-review-scanner.lock.yml`
- `.github/workflows/review-trigger.yml`
- `.github/docs/agent-labels.md`
🏁 Report — Final Recommendation
Comparative Fix Report — PR #36080
Candidates compared
| Rank | Candidate | Regression result | Summary |
|---|---|---|---|
| 1 | pr-plus-reviewer |
Applies the expert review feedback to the PR fix. It passes scan-time expected_head_sha and rerun_comment_id into review-trigger.yml, no-ops stale dispatches at the downstream trusted boundary, defers +1 until AzDO trigger success, and restores s/agent-ready-for-rerun on post-lock trigger failure. |
|
| 2 | try-fix-4 |
✅ Best available static result; Pester blocked | Functionally equivalent to pr-plus-reviewer and the strongest non-PR fallback. Ranked below pr-plus-reviewer only because the reviewer-applied PR-track candidate is the same solution integrated as direct feedback on the submitted fix. |
| 3 | try-fix-3 |
Adds downstream expected-head validation and clean stale no-op behavior, but still leaves the scanner's early +1 and can lose the ready label on downstream trigger failure. |
|
| 4 | try-fix-1 |
Moves freshness validation to review-trigger.yml, but stale dispatches fail the workflow instead of cleanly no-oping, and it still has early +1 / ready-label loss gaps. |
|
| 5 | try-fix-2 |
Re-reads the live head immediately before workflow dispatch, which narrows but does not close the race before review-trigger.yml lock/OIDC/AzDO work. |
|
| 6 | pr |
The submitted PR fixes the broad architecture problem by dispatching review-trigger.yml and replacing author_association trust with live permission lookup, but trigger freshness and acknowledgement remain incomplete. |
No candidate had a recorded regression-test failure; Pester was unavailable for the try-fix candidates and the gate was explicitly inconclusive. Therefore, ranking is based on code correctness and failure-mode coverage, not on treating inconclusive tests as failures.
Winning candidate
Winner: pr-plus-reviewer
pr-plus-reviewer wins because it keeps the PR's strong architectural refactor and applies the expert reviewer's two required corrections: final-boundary head-SHA freshness and transactional rerun acknowledgement/queue handling. It is strictly stronger than the raw PR and equivalent in behavior to try-fix-4, while staying on the PR-fix path.
Why the raw PR does not win
The raw PR validates decisions against scan-time candidates before dispatch, but does not pass that expected head SHA to the workflow that actually locks and triggers AzDO. A PR can advance between scanner validation and downstream workflow execution, causing review infrastructure to run on content the scanner did not validate. It also reacts +1 after createWorkflowDispatch succeeds, even though the downstream workflow may later no-op or fail before AzDO run creation.
Why the other try-fix candidates do not win
try-fix-1 and try-fix-3 both identify the right trusted boundary, but candidate 1 makes stale dispatches fail and candidate 3 still leaves false acceptance and queue-loss behavior. try-fix-2 is weaker because it only performs a pre-dispatch live head read in the scanner; the race remains open until review-trigger.yml starts. try-fix-4 is the best alternative and should be used as the fallback implementation if the PR-track reviewer feedback cannot be applied directly.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
Multi-model adversarial review — PR #36080 (round 3, commit
|
…confirmed' Round-3 review left one cosmetic note: the trigger path reacted '+1' (👍) after createWorkflowDispatch succeeds, which reads as 'accepted/done' even though that only means the dispatch was queued — review-trigger.yml owns the real AzDO run and may no-op downstream. Switch the trigger-path reaction to 'rocket' (🚀) so it reads as 'dispatched/launching' rather than 'completed'. The s/agent-ready-for-rerun label still persists, so a no-op downstream self-heals on the next scan. Skip path keeps 👎. Regenerated the .lock.yml via gh aw compile (frontmatter_hash refreshed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen — addressed the one remaining The trigger path now reacts 🚀 ( Kept it in-scope (no changes to |
Multi-model adversarial review — PR #36080 (round 4, commit
|
Round-4 review noted the .DESCRIPTION synopsis still said the trigger path reacts '+1', which went stale when the workflow switched to 'rocket' (7f75813). Zero behavioral impact (this script performs no reactions — it only emits the validated actions array), just keeps the doc-text in sync with the workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen — thanks for the Round-4 pass confirming the 🚀 change is clean. Addressed the one minor 💡 in |
Adversarial review — PR #36080 (round 5, commit
|
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!
Problem
The
/review rerunscanner (rerun-review-scanner) dispatched zero AzDO reviews after #35955, and the few it should dispatch would target the wrong branch. Two independent bugs:Bug 1 — every dispatch aborted with a spurious 404
In the gh-aw safe-output job, the
ghCLI returnsHTTP 404forrepos/dotnet/maui/pulls/N— even withpull-requests: writeon a public repo. #35955 misread these as transient and "hardened" the not-found guard, so it now faithfully confirms the bogus 404 and skips every PR. Evidence it is not a permission/token problem: thepre_activationjob (onlyMetadata: read) reads the same PRs fine, and the built-insafe_outputsjob (octokit) succeeds in the same run.Bug 2 — custom review branches silently downgraded to
mainThe candidate builder decided whose
/review -b <branch> -p <platform>to trust using the comment'sauthor_association. Under the ActionsGITHUB_TOKEN, a maintainer whose org membership is private reads asCONTRIBUTOR(notMEMBER), so their command was dropped and the rerun fell back to themainpipeline. A live scan confirmed it dispatched four net11 (feature/enhanced-reviewer) PRs onmain.Fix — do exactly what a maintainer
/reviewdoesBug 1: instead of re-implementing PR validation + OIDC + the AzDO trigger inside the safe job, the scanner now dispatches the same
review-trigger.ymlworkflow/reviewruns, viaworkflow_dispatch. That workflow owns PR validation, thes/agent-review-in-progresslock, platform inference, OIDC, and the AzDO trigger. (workflow_dispatchviaGITHUB_TOKENalways creates a run — it is exempt from Actions recursion-prevention.)Invoke-RerunReviewTrigger.ps1is now pure: validate batcheddecisionsagainstcandidates.json, emitactions.json. Nogh/AzDO/OIDC/lock/rate-limit I/O.github-script(octokit) step performs all GitHub writes — octokit works in the safe-job context where theghCLI does not.trigger→createWorkflowDispatch(review-trigger.yml,{pr_number,platform,pipeline_ref})+ 👍;skip→ 👎 + remove the queue label.+actions:write,−id-token:write; dropped theAZDO_TRIGGER_*secrets from the job.Bug 2: authorize
/reviewoptions by a live collaborator-permission lookup (collaborators/<user>/permission→ write/maintain/admin) — the exact callreview-trigger.yml's auth step makes. It only needsmetadata: read(every token has it) and reflects current access. No new secret or permission.Resolve-RerunEligibility.ps1: addTest-ReviewOptionLoginTrusted(cached per login);Get-LatestReviewCommandOptionscomputes trust through it. Removed theauthor_associationgate.Query-RerunReadyPRs.ps1: drop theauthor_associationhelpers; pass-Owner/-Repo.Validation
author_association=NONEcommand is still honored when the login has write access.createWorkflowDispatch, producing tworeview-trigger.ymlruns that triggered real AzDOmaui-copilotbuilds (HTTP 200, Run IDs 14459427 & 14459428).pipelineRef=feature/enhanced-reviewer(authorkubaflo,author_association=CONTRIBUTOR) instead ofmain, using the live permission lookup.Files
.github/scripts/Invoke-RerunReviewTrigger.ps1,.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1.github/scripts/Resolve-RerunEligibility.ps1,.github/scripts/Resolve-RerunEligibility.Tests.ps1.github/scripts/Query-RerunReadyPRs.ps1.github/workflows/rerun-review-scanner.md+ recompiled.lock.yml.github/docs/agent-labels.md🔍 This PR was created by an AI agent (GitHub Copilot CLI) on behalf of @kubaflo.