From 129b09d254c3e5f6257125276a2c099f4285cf2c Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:19:04 +0200 Subject: [PATCH 1/8] Restrict review rerun eligibility to author activity Only count PR author comments or new commits when determining whether /review rerun should apply the ready-for-rerun label. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/Query-RerunReadyPRs.ps1 | 6 ++- .../Resolve-RerunEligibility.Tests.ps1 | 47 ++++++++++++------- .github/scripts/Resolve-RerunEligibility.ps1 | 36 ++++++++++---- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/.github/scripts/Query-RerunReadyPRs.ps1 b/.github/scripts/Query-RerunReadyPRs.ps1 index 9aba7289a033..209bcddd0673 100644 --- a/.github/scripts/Query-RerunReadyPRs.ps1 +++ b/.github/scripts/Query-RerunReadyPRs.ps1 @@ -83,7 +83,7 @@ $searchResult = gh pr list ` --state open ` --label $ReadyForRerunLabel ` --limit $MaxPRs ` - --json number,title,url,headRefOid,isDraft,labels | ConvertFrom-Json + --json number,title,url,headRefOid,isDraft,labels,author | ConvertFrom-Json $candidates = @() foreach ($pr in @($searchResult)) { @@ -101,7 +101,8 @@ foreach ($pr in @($searchResult)) { $latestRerun = Get-LatestRerunComment -Comments $activity $reviewOptionAuthors = @(Get-ReviewOptionAuthorLogins -Comments $activity) $reviewOptions = Get-LatestReviewCommandOptions -Comments $activity -AllowedAuthorLogins $reviewOptionAuthors - $contextMarkdown = New-RerunContextMarkdown -Comments $activity -Commits $commits -CurrentHeadSha $pr.headRefOid -CurrentLabels $labels + $authorLogin = if ($pr.author -and $pr.author.login) { [string]$pr.author.login } else { '' } + $contextMarkdown = New-RerunContextMarkdown -Comments $activity -Commits $commits -CurrentHeadSha $pr.headRefOid -PRAuthorLogin $authorLogin -CurrentLabels $labels $platform = if ($reviewOptions.Platform) { $reviewOptions.Platform } else { Get-PlatformFromLabels -Labels $labels } $pipelineRef = if ($reviewOptions.PipelineRef) { $reviewOptions.PipelineRef } else { 'main' } @@ -109,6 +110,7 @@ foreach ($pr in @($searchResult)) { prNumber = $number title = [string]$pr.title url = [string]$pr.url + authorLogin = $authorLogin isDraft = [bool]$pr.isDraft headSha = [string]$pr.headRefOid platform = $platform diff --git a/.github/scripts/Resolve-RerunEligibility.Tests.ps1 b/.github/scripts/Resolve-RerunEligibility.Tests.ps1 index c8fe02c34c59..8ec61b87788e 100644 --- a/.github/scripts/Resolve-RerunEligibility.Tests.ps1 +++ b/.github/scripts/Resolve-RerunEligibility.Tests.ps1 @@ -193,17 +193,30 @@ Describe 'Resolve-RerunEligibility' { $result.Reason | Should -Be 'no-new-comments-or-commits' } - It 'accepts a non-command comment after the latest AI Summary' { + It 'accepts a non-command PR author comment after the latest AI Summary' { $comments = @( New-TestComment -Id 1 -Body (New-AISummaryBody) -CreatedAt '2026-05-31T09:00:00Z' -UpdatedAt '2026-05-31T09:30:00Z' -Login 'MauiBot' -Type 'User' New-TestComment -Id 2 -Body 'I pushed the requested update.' -CreatedAt '2026-05-31T09:45:00Z' New-TestComment -Id 10 -Body '/review rerun' -CreatedAt '2026-05-31T10:00:00Z' ) - $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' -PRAuthorLogin 'dev-user' $result.Eligible | Should -BeTrue - $result.Reason | Should -Be 'new-comment-after-ai-summary' + $result.Reason | Should -Be 'new-author-comment-after-ai-summary' + } + + It 'rejects a non-author maintainer comment after the latest AI Summary' { + $comments = @( + New-TestComment -Id 1 -Body (New-AISummaryBody) -CreatedAt '2026-05-31T09:00:00Z' -UpdatedAt '2026-05-31T09:30:00Z' -Login 'MauiBot' -Type 'User' + New-TestComment -Id 2 -Body 'Could you please check the AI suggestions?' -CreatedAt '2026-05-31T09:45:00Z' -Login 'kubaflo' -Kind 'review' + New-TestComment -Id 10 -Body '/review rerun' -CreatedAt '2026-05-31T10:00:00Z' -Login 'kubaflo' + ) + + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' -PRAuthorLogin 'dev-user' + + $result.Eligible | Should -BeFalse + $result.Reason | Should -Be 'no-new-comments-or-commits' } It 'uses AI Summary creation time as the activity checkpoint when the summary was edited later' { @@ -213,10 +226,10 @@ Describe 'Resolve-RerunEligibility' { New-TestComment -Id 10 -Body '/review rerun' -CreatedAt '2026-05-31T10:00:00Z' ) - $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' -PRAuthorLogin 'dev-user' $result.Eligible | Should -BeTrue - $result.Reason | Should -Be 'new-comment-after-ai-summary' + $result.Reason | Should -Be 'new-author-comment-after-ai-summary' } It 'selects the newest AI Summary by creation time instead of edit time' { @@ -227,10 +240,10 @@ Describe 'Resolve-RerunEligibility' { New-TestComment -Id 10 -Body '/review rerun' -CreatedAt '2026-05-31T10:30:00Z' ) - $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha '2222222abcdef' + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha '2222222abcdef' -PRAuthorLogin 'dev-user' $result.Eligible | Should -BeTrue - $result.Reason | Should -Be 'new-comment-after-ai-summary' + $result.Reason | Should -Be 'new-author-comment-after-ai-summary' } It 'ignores forged AI Summary comments from non-bots' { @@ -277,10 +290,10 @@ Describe 'Resolve-RerunEligibility' { New-TestComment -Id 4659999999 -Body '/review rerun' -CreatedAt '2026-06-09T09:00:00Z' ) - $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 4659999999 -CurrentHeadSha '6e9af5bc8b5d0023400d653500951fb46df44170' + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 4659999999 -CurrentHeadSha '6e9af5bc8b5d0023400d653500951fb46df44170' -PRAuthorLogin 'dev-user' $result.Eligible | Should -BeTrue - $result.Reason | Should -Be 'new-comment-after-ai-summary' + $result.Reason | Should -Be 'new-author-comment-after-ai-summary' } It 'uses the first session marker from an AI Summary' { @@ -308,7 +321,7 @@ new $result.Reason | Should -Be 'no-new-comments-or-commits' } - It 'accepts a non-command comment after the previous rerun command' { + It 'accepts a non-command PR author comment after the previous rerun command' { $comments = @( New-TestComment -Id 1 -Body (New-AISummaryBody) -CreatedAt '2026-05-31T09:00:00Z' -UpdatedAt '2026-05-31T09:30:00Z' -Login 'MauiBot' -Type 'User' New-TestComment -Id 8 -Body '/review rerun' -CreatedAt '2026-05-31T09:45:00Z' @@ -316,10 +329,10 @@ new New-TestComment -Id 10 -Body '/review rerun' -CreatedAt '2026-05-31T10:00:00Z' ) - $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' -PRAuthorLogin 'dev-user' $result.Eligible | Should -BeTrue - $result.Reason | Should -Be 'new-comment-after-previous-rerun' + $result.Reason | Should -Be 'new-author-comment-after-previous-rerun' } It 'does not reuse old activity from before a previous rerun command' { @@ -343,10 +356,10 @@ new New-TestComment -Id 10 -Body '/review rerun' -CreatedAt '2026-05-31T10:00:00Z' ) - $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' + $result = Resolve-RerunEligibility -Comments $comments -Commits @() -CurrentCommentId 10 -CurrentHeadSha 'abcdef123' -PRAuthorLogin 'dev-user' $result.Eligible | Should -BeTrue - $result.Reason | Should -Be 'new-comment-after-ai-summary' + $result.Reason | Should -Be 'new-author-comment-after-ai-summary' } It 'accepts a current head SHA that differs from the latest reviewed session' { @@ -418,20 +431,22 @@ new $comments = @( New-TestComment -Id 1 -Body (New-AISummaryBody) -CreatedAt '2026-05-31T09:00:00Z' -UpdatedAt '2026-05-31T09:30:00Z' -Login 'MauiBot' -Type 'User' New-TestComment -Id 2 -Body 'New author context.' -CreatedAt '2026-05-31T09:45:00Z' + New-TestComment -Id 11 -Body 'Reviewer reminder.' -CreatedAt '2026-05-31T09:46:00Z' -Login 'reviewer' New-TestComment -Id 3 -Body '/review rerun' -CreatedAt '2026-05-31T09:50:00Z' ) $commits = @( New-TestCommit -Sha 'fedcba9876543210' -Date '2026-05-31T09:48:00Z' ) - $context = New-RerunContextMarkdown -Comments $comments -Commits $commits -CurrentHeadSha 'fedcba9876543210' -CurrentLabels @('s/agent-review-in-progress') + $context = New-RerunContextMarkdown -Comments $comments -Commits $commits -CurrentHeadSha 'fedcba9876543210' -PRAuthorLogin 'dev-user' -CurrentLabels @('s/agent-review-in-progress') $context | Should -Match '# Rerun Context' - $context | Should -Match 'New non-command comments: 1' + $context | Should -Match 'New non-command author comments: 1' $context | Should -Match 'New commits: 1' $context | Should -Match '`s/agent-ready-for-rerun` present: false' $context | Should -Match '`s/agent-review-in-progress` present: true' $context | Should -Match 'New author context' + $context | Should -Not -Match 'Reviewer reminder' $context | Should -Match 'fedcba9' $context | Should -Not -Match '\| .*\/review rerun' } diff --git a/.github/scripts/Resolve-RerunEligibility.ps1 b/.github/scripts/Resolve-RerunEligibility.ps1 index 0b8ebcaaf48f..9899214f35e1 100644 --- a/.github/scripts/Resolve-RerunEligibility.ps1 +++ b/.github/scripts/Resolve-RerunEligibility.ps1 @@ -6,8 +6,8 @@ .DESCRIPTION This script is intentionally deterministic: it never uses AI and never inspects untrusted text semantically. A rerun is eligible only when there is - new PR activity after the previous AI Summary or previous /review rerun: - a new non-command comment, or a new commit. + new PR-author activity after the previous AI Summary or previous /review rerun: + a new non-command PR-author comment, or a new commit. #> param( @@ -27,7 +27,7 @@ $ErrorActionPreference = 'Stop' $AISummaryMarker = '' $ReadyForRerunLabel = 's/agent-ready-for-rerun' $ReviewInProgressLabel = 's/agent-review-in-progress' -$ReadyForRerunLabelDescription = 'AI review has new PR activity and is ready for rerun' +$ReadyForRerunLabelDescription = 'AI review has a new PR-author comment or commit and is ready for rerun' $ReadyForRerunLabelColor = '5319E7' $AISummaryAuthorLogins = @( 'MauiBot' @@ -279,12 +279,22 @@ function Get-LatestReviewedSha { function Test-CommentIsEvidence { param( [Parameter(Mandatory = $true)]$Comment, - [Parameter(Mandatory = $true)][Int64]$CurrentCommentId + [Parameter(Mandatory = $true)][Int64]$CurrentCommentId, + [string]$PRAuthorLogin ) if ([Int64]$Comment.id -eq $CurrentCommentId) { return $false } + if ([string]::IsNullOrWhiteSpace($PRAuthorLogin)) { + return $false + } + if (-not $Comment.user -or [string]::IsNullOrWhiteSpace([string]$Comment.user.login)) { + return $false + } + if (-not ([string]$Comment.user.login).Equals($PRAuthorLogin, [StringComparison]::OrdinalIgnoreCase)) { + return $false + } if (Test-RerunCommand $Comment.body) { return $false } @@ -302,11 +312,12 @@ function Test-HasEvidenceCommentAfter { param( [object[]]$Comments, [Parameter(Mandatory = $true)][datetimeoffset]$Checkpoint, - [Parameter(Mandatory = $true)][Int64]$CurrentCommentId + [Parameter(Mandatory = $true)][Int64]$CurrentCommentId, + [string]$PRAuthorLogin ) return [bool]@($Comments | Where-Object { - (Test-CommentIsEvidence -Comment $_ -CurrentCommentId $CurrentCommentId) -and + (Test-CommentIsEvidence -Comment $_ -CurrentCommentId $CurrentCommentId -PRAuthorLogin $PRAuthorLogin) -and (Get-ObjectDate $_ 'created_at') -gt $Checkpoint } | Select-Object -First 1) } @@ -401,6 +412,7 @@ function New-RerunContextMarkdown { [object[]]$Comments, [object[]]$Commits, [string]$CurrentHeadSha, + [string]$PRAuthorLogin, [object[]]$CurrentLabels = @() ) @@ -426,7 +438,7 @@ function New-RerunContextMarkdown { $evidenceComments = @() if ($checkpoint) { $evidenceComments = @($Comments | Where-Object { - (Test-CommentIsEvidence -Comment $_ -CurrentCommentId 0) -and + (Test-CommentIsEvidence -Comment $_ -CurrentCommentId 0 -PRAuthorLogin $PRAuthorLogin) -and (Get-ObjectDate $_ 'created_at') -gt $checkpoint } | Sort-Object @{ Expression = { Get-ObjectDate $_ 'created_at' }; Descending = $false }, @{ Expression = { [Int64]$_.id }; Descending = $false }) } @@ -465,6 +477,7 @@ function New-RerunContextMarkdown { } else { $lines.Add('- Activity checkpoint: none') } + $lines.Add("- PR author: $(if ([string]::IsNullOrWhiteSpace($PRAuthorLogin)) { 'unknown' } else { $PRAuthorLogin })") $lines.Add("- Latest reviewed SHA: $(if ($latestReviewedSha) { $latestReviewedSha } else { 'unknown' })") $lines.Add("- Current head SHA: $(if ($CurrentHeadSha) { $CurrentHeadSha } else { 'unknown' })") $lines.Add("- Current head differs from latest reviewed SHA: $($headDiffers.ToString().ToLowerInvariant())") @@ -473,7 +486,7 @@ function New-RerunContextMarkdown { $lines.Add('') $lines.Add('## New activity since checkpoint') $lines.Add('') - $lines.Add("- New non-command comments: $($evidenceComments.Count)") + $lines.Add("- New non-command author comments: $($evidenceComments.Count)") $lines.Add("- New commits: $($newCommits.Count)") $lines.Add('') @@ -519,6 +532,7 @@ function Resolve-RerunEligibility { [object[]]$Commits, [Parameter(Mandatory = $true)][Int64]$CurrentCommentId, [string]$CurrentHeadSha, + [string]$PRAuthorLogin, [object[]]$CurrentLabels = @() ) @@ -565,8 +579,8 @@ function Resolve-RerunEligibility { return [pscustomobject]@{ Eligible = $true; Reason = 'new-head-commit'; Label = $ReadyForRerunLabel } } - if (Test-HasEvidenceCommentAfter -Comments $Comments -Checkpoint $checkpoint -CurrentCommentId $CurrentCommentId) { - $reason = if ($checkpointReason -eq 'previous-rerun') { 'new-comment-after-previous-rerun' } else { 'new-comment-after-ai-summary' } + if (Test-HasEvidenceCommentAfter -Comments $Comments -Checkpoint $checkpoint -CurrentCommentId $CurrentCommentId -PRAuthorLogin $PRAuthorLogin) { + $reason = if ($checkpointReason -eq 'previous-rerun') { 'new-author-comment-after-previous-rerun' } else { 'new-author-comment-after-ai-summary' } return [pscustomobject]@{ Eligible = $true; Reason = $reason; Label = $ReadyForRerunLabel } } @@ -603,6 +617,7 @@ if ($ContextOutputPath) { -Comments $comments ` -Commits $commits ` -CurrentHeadSha $pr.head.sha ` + -PRAuthorLogin $pr.user.login ` -CurrentLabels $labels $contextDir = Split-Path -Parent $ContextOutputPath if ($contextDir) { @@ -627,6 +642,7 @@ $result = Resolve-RerunEligibility ` -Commits $commits ` -CurrentCommentId $CurrentCommentId ` -CurrentHeadSha $pr.head.sha ` + -PRAuthorLogin $pr.user.login ` -CurrentLabels $labels Write-Host "Rerun eligibility: $($result.Eligible) ($($result.Reason))" From 7aa823e7c1c34110a6b471ff84b2ec05a6b6f922 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:36:49 +0200 Subject: [PATCH 2/8] Fix rerun scanner candidate prompt handoff Build rerun scanner candidates during gh-aw pre-activation and expose them through pre-activation outputs so the agent prompt receives the generated candidate JSON. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/rerun-review-scanner.lock.yml | 156 ++++++++++++++---- .github/workflows/rerun-review-scanner.md | 71 ++++---- 2 files changed, 160 insertions(+), 67 deletions(-) diff --git a/.github/workflows/rerun-review-scanner.lock.yml b/.github/workflows/rerun-review-scanner.lock.yml index c67521c099dc..6060c4deff2c 100644 --- a/.github/workflows/rerun-review-scanner.lock.yml +++ b/.github/workflows/rerun-review-scanner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2b9a2782d3cbe381a033db506d34d2bccce35ba8ef4668736e340068ccddbb4f","body_hash":"75dc74a551ad2ba0c8b1056bda890bce75dc6fa3ebf0536ad5a4eb46a61dcd5a","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"02b7e0d3cab8f9746ede753697b2e129d0ec98f8f909b2c3d41e45bf734ff544","body_hash":"bb8c799323c000bdf2eba2eb374a7843dc3066beae6dfc7337c6e0d54c884c10","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["AZDO_TRIGGER_CLIENT_ID","AZDO_TRIGGER_TENANT_ID","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -53,6 +53,38 @@ name: "Rerun Review Scanner" on: schedule: - cron: "0 * * * *" + # steps: # Steps injected into pre-activation job + # - env: + # GH_TOKEN: ${{ github.token }} + # MAX_PRS: ${{ inputs.max_prs || '5' }} + # REPO_NAME: ${{ github.event.repository.name }} + # REPO_OWNER: ${{ github.repository_owner }} + # id: rerun_context + # name: Build rerun candidate context + # run: | + # $max = 5 + # if ($env:MAX_PRS -match '^\d+$') { + # $max = [Math]::Max(1, [Math]::Min(20, [int]$env:MAX_PRS)) + # } + # $output = "CustomAgentLogsTmp/RerunScanner/candidates.json" + # .github/scripts/Query-RerunReadyPRs.ps1 ` + # -Owner $env:REPO_OWNER ` + # -Repo $env:REPO_NAME ` + # -MaxPRs $max ` + # -OutputPath $output | Out-Null + # $json = Get-Content -Raw -LiteralPath $output + # $delimiter = "EOF_$([Guid]::NewGuid().ToString('N'))" + # "candidates<<$delimiter" >> $env:GITHUB_OUTPUT + # $json >> $env:GITHUB_OUTPUT + # $delimiter >> $env:GITHUB_OUTPUT + # shell: pwsh + # - name: Upload rerun candidate context + # uses: actions/upload-artifact@v7.0.1 + # with: + # if-no-files-found: error + # name: rerun-candidates + # path: CustomAgentLogsTmp/RerunScanner/candidates.json + # retention-days: 1 workflow_dispatch: inputs: aw_context: @@ -81,6 +113,8 @@ run-name: "Rerun Review Scanner" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read @@ -103,6 +137,8 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Rerun Review Scanner" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/rerun-review-scanner.lock.yml@${{ github.ref }} @@ -196,25 +232,25 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_STEPS_RERUN_CONTEXT_OUTPUTS_CANDIDATES: ${{ steps.rerun_context.outputs.candidates }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_RERUN_CANDIDATES: ${{ needs.pre_activation.outputs.rerun_candidates }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF' + cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' - GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF + GH_AW_PROMPT_fcbdc87c901a718e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF' + cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' Tools: missing_tool, missing_data, noop, trigger_rerun_review - GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF + GH_AW_PROMPT_fcbdc87c901a718e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF' + cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -243,19 +279,19 @@ jobs: {{/if}} - GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF + GH_AW_PROMPT_fcbdc87c901a718e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF' + cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' {{#runtime-import .github/workflows/rerun-review-scanner.md}} - GH_AW_PROMPT_c5f8a4ab1dfba5c0_EOF + GH_AW_PROMPT_fcbdc87c901a718e_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_STEPS_RERUN_CONTEXT_OUTPUTS_CANDIDATES: ${{ steps.rerun_context.outputs.candidates }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_RERUN_CANDIDATES: ${{ needs.pre_activation.outputs.rerun_candidates }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -275,7 +311,8 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' - GH_AW_STEPS_RERUN_CONTEXT_OUTPUTS_CANDIDATES: ${{ steps.rerun_context.outputs.candidates }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_RERUN_CANDIDATES: ${{ needs.pre_activation.outputs.rerun_candidates }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -296,7 +333,8 @@ jobs: GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, - GH_AW_STEPS_RERUN_CONTEXT_OUTPUTS_CANDIDATES: process.env.GH_AW_STEPS_RERUN_CONTEXT_OUTPUTS_CANDIDATES + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_RERUN_CANDIDATES: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_RERUN_CANDIDATES } }); - name: Validate prompt placeholders @@ -393,23 +431,6 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - - env: - GH_TOKEN: ${{ github.token }} - MAX_PRS: ${{ inputs.max_prs || '5' }} - REPO_NAME: ${{ github.event.repository.name }} - REPO_OWNER: ${{ github.repository_owner }} - id: rerun_context - name: Build rerun candidate context - run: "$max = 5\nif ($env:MAX_PRS -match '^\\d+$') {\n $max = [Math]::Max(1, [Math]::Min(20, [int]$env:MAX_PRS))\n}\n$output = \"CustomAgentLogsTmp/RerunScanner/candidates.json\"\n.github/scripts/Query-RerunReadyPRs.ps1 `\n -Owner $env:REPO_OWNER `\n -Repo $env:REPO_NAME `\n -MaxPRs $max `\n -OutputPath $output | Out-Null\n$json = Get-Content -Raw -LiteralPath $output\n$delimiter = \"EOF_$([Guid]::NewGuid().ToString('N'))\"\n\"candidates<<$delimiter\" >> $env:GITHUB_OUTPUT\n$json >> $env:GITHUB_OUTPUT\n$delimiter >> $env:GITHUB_OUTPUT\n" - shell: pwsh - - name: Upload rerun candidate context - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - if-no-files-found: error - name: rerun-candidates - path: CustomAgentLogsTmp/RerunScanner/candidates.json - retention-days: 1 - - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} @@ -480,9 +501,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b87c598b007edcc9_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_cc940a56302172a1_EOF' {"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"trigger-rerun-review":{"description":"Apply a validated rerun scanner decision. Use once per candidate PR with decision 'trigger' or 'skip'.","inputs":{"decision":{"default":null,"description":"Whether to trigger or skip the rerun","options":["trigger","skip"],"required":true,"type":"choice"},"expected_head_sha":{"default":null,"description":"Current PR head SHA observed by the scanner","required":true,"type":"string"},"pipeline_ref":{"default":null,"description":"AzDO pipeline branch/ref to use for the rerun","required":false,"type":"string"},"platform":{"default":null,"description":"Optional target platform; leave empty to infer from labels","required":false,"type":"string"},"pr_number":{"default":null,"description":"Pull request number to process","required":true,"type":"string"},"reason":{"default":null,"description":"Short deterministic-safe reason for the decision","required":true,"type":"string"},"rerun_comment_id":{"default":null,"description":"Issue comment ID for the /review rerun command","required":true,"type":"string"}},"output":"Rerun scanner decision processed."}} - GH_AW_SAFE_OUTPUTS_CONFIG_b87c598b007edcc9_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_cc940a56302172a1_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -702,7 +723,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_953a0c607e8bafff_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_1c2ebd35f89e1af7_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -743,7 +764,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_953a0c607e8bafff_EOF + GH_AW_MCP_CONFIG_1c2ebd35f89e1af7_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1350,6 +1371,73 @@ jobs: } } + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + rerun_candidates: ${{ steps.rerun_context.outputs.candidates }} + rerun_context_result: ${{ steps.rerun_context.outcome }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Rerun Review Scanner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/rerun-review-scanner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + - name: Build rerun candidate context + id: rerun_context + run: | + $max = 5 + if ($env:MAX_PRS -match '^\d+$') { + $max = [Math]::Max(1, [Math]::Min(20, [int]$env:MAX_PRS)) + } + $output = "CustomAgentLogsTmp/RerunScanner/candidates.json" + .github/scripts/Query-RerunReadyPRs.ps1 ` + -Owner $env:REPO_OWNER ` + -Repo $env:REPO_NAME ` + -MaxPRs $max ` + -OutputPath $output | Out-Null + $json = Get-Content -Raw -LiteralPath $output + $delimiter = "EOF_$([Guid]::NewGuid().ToString('N'))" + "candidates<<$delimiter" >> $env:GITHUB_OUTPUT + $json >> $env:GITHUB_OUTPUT + $delimiter >> $env:GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + MAX_PRS: ${{ inputs.max_prs || '5' }} + REPO_NAME: ${{ github.event.repository.name }} + REPO_OWNER: ${{ github.repository_owner }} + shell: pwsh + - name: Upload rerun candidate context + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + if-no-files-found: error + name: rerun-candidates + path: CustomAgentLogsTmp/RerunScanner/candidates.json + retention-days: 1 + safe_outputs: needs: - activation diff --git a/.github/workflows/rerun-review-scanner.md b/.github/workflows/rerun-review-scanner.md index 72ebd2039b46..cfd93938cdb6 100644 --- a/.github/workflows/rerun-review-scanner.md +++ b/.github/workflows/rerun-review-scanner.md @@ -14,12 +14,49 @@ on: required: false type: number default: 5 + steps: + - name: Build rerun candidate context + id: rerun_context + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + MAX_PRS: ${{ inputs.max_prs || '5' }} + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + run: | + $max = 5 + if ($env:MAX_PRS -match '^\d+$') { + $max = [Math]::Max(1, [Math]::Min(20, [int]$env:MAX_PRS)) + } + $output = "CustomAgentLogsTmp/RerunScanner/candidates.json" + .github/scripts/Query-RerunReadyPRs.ps1 ` + -Owner $env:REPO_OWNER ` + -Repo $env:REPO_NAME ` + -MaxPRs $max ` + -OutputPath $output | Out-Null + $json = Get-Content -Raw -LiteralPath $output + $delimiter = "EOF_$([Guid]::NewGuid().ToString('N'))" + "candidates<<$delimiter" >> $env:GITHUB_OUTPUT + $json >> $env:GITHUB_OUTPUT + $delimiter >> $env:GITHUB_OUTPUT + - name: Upload rerun candidate context + uses: actions/upload-artifact@v7.0.1 + with: + name: rerun-candidates + path: CustomAgentLogsTmp/RerunScanner/candidates.json + if-no-files-found: error + retention-days: 1 permissions: contents: read issues: read pull-requests: read +jobs: + pre-activation: + outputs: + rerun_candidates: ${{ steps.rerun_context.outputs.candidates }} + concurrency: # Serialize scheduled and manual scanner runs so each queued PR is evaluated # against the latest label/head/lock state before any safe-output job can trigger. @@ -103,38 +140,6 @@ safe-outputs: } .github/scripts/Invoke-RerunReviewTrigger.ps1 @scriptArgs -steps: - - name: Build rerun candidate context - id: rerun_context - shell: pwsh - env: - GH_TOKEN: ${{ github.token }} - MAX_PRS: ${{ inputs.max_prs || '5' }} - REPO_OWNER: ${{ github.repository_owner }} - REPO_NAME: ${{ github.event.repository.name }} - run: | - $max = 5 - if ($env:MAX_PRS -match '^\d+$') { - $max = [Math]::Max(1, [Math]::Min(20, [int]$env:MAX_PRS)) - } - $output = "CustomAgentLogsTmp/RerunScanner/candidates.json" - .github/scripts/Query-RerunReadyPRs.ps1 ` - -Owner $env:REPO_OWNER ` - -Repo $env:REPO_NAME ` - -MaxPRs $max ` - -OutputPath $output | Out-Null - $json = Get-Content -Raw -LiteralPath $output - $delimiter = "EOF_$([Guid]::NewGuid().ToString('N'))" - "candidates<<$delimiter" >> $env:GITHUB_OUTPUT - $json >> $env:GITHUB_OUTPUT - $delimiter >> $env:GITHUB_OUTPUT - - name: Upload rerun candidate context - uses: actions/upload-artifact@v7.0.1 - with: - name: rerun-candidates - path: CustomAgentLogsTmp/RerunScanner/candidates.json - if-no-files-found: error - retention-days: 1 --- # Rerun Review Scanner @@ -175,7 +180,7 @@ using a global concurrency group that could cancel unrelated maintainer The deterministic scanner found these candidates: ```json -${{ steps.rerun_context.outputs.candidates }} +${{ needs.pre_activation.outputs.rerun_candidates }} ``` For each candidate in `candidates`: From ace437309cdd153922ec42d93b42dd421b14c3ef Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:38:40 +0200 Subject: [PATCH 3/8] Checkout scripts before rerun scanner pre-activation The scanner now builds candidate context during gh-aw pre-activation, so explicitly checkout the repository before invoking the helper scripts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/rerun-review-scanner.lock.yml | 34 ++++++++++++------- .github/workflows/rerun-review-scanner.md | 4 +++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/rerun-review-scanner.lock.yml b/.github/workflows/rerun-review-scanner.lock.yml index 6060c4deff2c..aca5972bde6c 100644 --- a/.github/workflows/rerun-review-scanner.lock.yml +++ b/.github/workflows/rerun-review-scanner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"02b7e0d3cab8f9746ede753697b2e129d0ec98f8f909b2c3d41e45bf734ff544","body_hash":"bb8c799323c000bdf2eba2eb374a7843dc3066beae6dfc7337c6e0d54c884c10","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1202c20eb8b072cbb54e89173c40201cba38719b2d13e3dfd7517fc32303375d","body_hash":"bb8c799323c000bdf2eba2eb374a7843dc3066beae6dfc7337c6e0d54c884c10","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["AZDO_TRIGGER_CLIENT_ID","AZDO_TRIGGER_TENANT_ID","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -54,6 +54,10 @@ on: schedule: - cron: "0 * * * *" # steps: # Steps injected into pre-activation job + # - name: Checkout repository scripts + # uses: actions/checkout@v4 + # with: + # persist-credentials: false # - env: # GH_TOKEN: ${{ github.token }} # MAX_PRS: ${{ inputs.max_prs || '5' }} @@ -237,20 +241,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' + cat << 'GH_AW_PROMPT_cae32697d63d8db5_EOF' - GH_AW_PROMPT_fcbdc87c901a718e_EOF + GH_AW_PROMPT_cae32697d63d8db5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' + cat << 'GH_AW_PROMPT_cae32697d63d8db5_EOF' Tools: missing_tool, missing_data, noop, trigger_rerun_review - GH_AW_PROMPT_fcbdc87c901a718e_EOF + GH_AW_PROMPT_cae32697d63d8db5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' + cat << 'GH_AW_PROMPT_cae32697d63d8db5_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -279,12 +283,12 @@ jobs: {{/if}} - GH_AW_PROMPT_fcbdc87c901a718e_EOF + GH_AW_PROMPT_cae32697d63d8db5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_fcbdc87c901a718e_EOF' + cat << 'GH_AW_PROMPT_cae32697d63d8db5_EOF' {{#runtime-import .github/workflows/rerun-review-scanner.md}} - GH_AW_PROMPT_fcbdc87c901a718e_EOF + GH_AW_PROMPT_cae32697d63d8db5_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -501,9 +505,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_cc940a56302172a1_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4cac70a2707e027b_EOF' {"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"trigger-rerun-review":{"description":"Apply a validated rerun scanner decision. Use once per candidate PR with decision 'trigger' or 'skip'.","inputs":{"decision":{"default":null,"description":"Whether to trigger or skip the rerun","options":["trigger","skip"],"required":true,"type":"choice"},"expected_head_sha":{"default":null,"description":"Current PR head SHA observed by the scanner","required":true,"type":"string"},"pipeline_ref":{"default":null,"description":"AzDO pipeline branch/ref to use for the rerun","required":false,"type":"string"},"platform":{"default":null,"description":"Optional target platform; leave empty to infer from labels","required":false,"type":"string"},"pr_number":{"default":null,"description":"Pull request number to process","required":true,"type":"string"},"reason":{"default":null,"description":"Short deterministic-safe reason for the decision","required":true,"type":"string"},"rerun_comment_id":{"default":null,"description":"Issue comment ID for the /review rerun command","required":true,"type":"string"}},"output":"Rerun scanner decision processed."}} - GH_AW_SAFE_OUTPUTS_CONFIG_cc940a56302172a1_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_4cac70a2707e027b_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -723,7 +727,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_1c2ebd35f89e1af7_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_c266c93f4f8f2521_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -764,7 +768,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_1c2ebd35f89e1af7_EOF + GH_AW_MCP_CONFIG_c266c93f4f8f2521_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1406,6 +1410,10 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); await main(); + - name: Checkout repository scripts + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Build rerun candidate context id: rerun_context run: | diff --git a/.github/workflows/rerun-review-scanner.md b/.github/workflows/rerun-review-scanner.md index cfd93938cdb6..8f8cdb0da13f 100644 --- a/.github/workflows/rerun-review-scanner.md +++ b/.github/workflows/rerun-review-scanner.md @@ -15,6 +15,10 @@ on: type: number default: 5 steps: + - name: Checkout repository scripts + uses: actions/checkout@v4 + with: + persist-credentials: false - name: Build rerun candidate context id: rerun_context shell: pwsh From 65790167f785c7980272dfaa9b427bd6ca5bf61e Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:45:21 +0200 Subject: [PATCH 4/8] Handle stale rerun scanner PR decisions cleanly Treat PR lookup failures as stale scanner decisions and exit successfully after handled safe-output items so non-fatal skip paths do not fail the workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/Invoke-RerunReviewTrigger.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/scripts/Invoke-RerunReviewTrigger.ps1 b/.github/scripts/Invoke-RerunReviewTrigger.ps1 index 00313489a541..2ba823bfe160 100644 --- a/.github/scripts/Invoke-RerunReviewTrigger.ps1 +++ b/.github/scripts/Invoke-RerunReviewTrigger.ps1 @@ -311,7 +311,13 @@ foreach ($item in $items) { } Write-Host "Processing PR #$prNumber decision=$decision reason=$(ConvertTo-SafeLogValue $reason)" - $pr = gh api "repos/$Owner/$Repo/pulls/$prNumber" | ConvertFrom-Json + $prJson = & gh api "repos/$Owner/$Repo/pulls/$prNumber" 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($prJson)) { + $global:LASTEXITCODE = 0 + Write-Host " ⏭️ PR #$prNumber could not be loaded; skipping stale decision" + continue + } + $pr = $prJson | ConvertFrom-Json if ($pr.state -ne 'open') { Write-Host " ⏭️ PR #$prNumber is not open ($($pr.state)); skipping" continue @@ -402,3 +408,5 @@ foreach ($item in $items) { continue } } + +exit 0 From 3939e24ad696b2e29041e625912b2d0f12afab01 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:45:55 +0200 Subject: [PATCH 5/8] Address rerun scanner review feedback Normalize app-style bot author logins before generating rerun context, and fail visible for non-stale PR lookup errors instead of treating every gh failure as a stale PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Invoke-RerunReviewTrigger.Tests.ps1 | 16 ++++++++- .github/scripts/Invoke-RerunReviewTrigger.ps1 | 35 +++++++++++++++---- .github/scripts/Query-RerunReadyPRs.ps1 | 3 +- .../Resolve-RerunEligibility.Tests.ps1 | 20 +++++++++++ .github/scripts/Resolve-RerunEligibility.ps1 | 27 +++++++++++--- 5 files changed, 89 insertions(+), 12 deletions(-) diff --git a/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 b/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 index af0ef3343055..b6b2a0e55a52 100644 --- a/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 +++ b/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 @@ -14,7 +14,7 @@ BeforeAll { $script:ReviewTriggerWindowHours = 24 $script:MaxReviewTriggersPerWindow = 3 - foreach ($functionName in @('Get-ReviewTriggerRateLimitStatus', 'ConvertTo-SafeLogValue', 'Get-MatchingCandidate', 'Normalize-PipelineRef', 'Get-PlatformFromLabels')) { + foreach ($functionName in @('Get-ReviewTriggerRateLimitStatus', 'ConvertTo-SafeLogValue', 'Test-GhApiPrNotFound', 'Get-MatchingCandidate', 'Normalize-PipelineRef', 'Get-PlatformFromLabels')) { $function = $ast.Find({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $args[0].Name -eq $functionName @@ -73,6 +73,20 @@ Describe 'ConvertTo-SafeLogValue' { } } +Describe 'Test-GhApiPrNotFound' { + It 'recognizes stale PR responses' { + Test-GhApiPrNotFound 'gh: Not Found (HTTP 404)' | Should -BeTrue + Test-GhApiPrNotFound 'gh: Gone (HTTP 410)' | Should -BeTrue + } + + It 'does not hide credential, rate-limit, or transient failures' { + Test-GhApiPrNotFound 'gh: Bad credentials (HTTP 401)' | Should -BeFalse + Test-GhApiPrNotFound 'gh: API rate limit exceeded (HTTP 403)' | Should -BeFalse + Test-GhApiPrNotFound 'gh: Internal Server Error (HTTP 500)' | Should -BeFalse + Test-GhApiPrNotFound '' | Should -BeFalse + } +} + Describe 'Get-MatchingCandidate' { It 'matches only PRs in the deterministic candidate set' { $candidates = @( diff --git a/.github/scripts/Invoke-RerunReviewTrigger.ps1 b/.github/scripts/Invoke-RerunReviewTrigger.ps1 index 2ba823bfe160..fb07576475bf 100644 --- a/.github/scripts/Invoke-RerunReviewTrigger.ps1 +++ b/.github/scripts/Invoke-RerunReviewTrigger.ps1 @@ -68,6 +68,16 @@ function ConvertTo-SafeLogValue { return $safe } +function Test-GhApiPrNotFound { + param([string]$Output) + + if ([string]::IsNullOrWhiteSpace($Output)) { + return $false + } + + return $Output -match '(?i)\bHTTP\s+(404|410)\b' -or $Output -match '(?i)\b(Not Found|Gone)\b' +} + function Add-CommentReaction { param( [Parameter(Mandatory = $true)][Int64]$CommentId, @@ -311,13 +321,26 @@ foreach ($item in $items) { } Write-Host "Processing PR #$prNumber decision=$decision reason=$(ConvertTo-SafeLogValue $reason)" - $prJson = & gh api "repos/$Owner/$Repo/pulls/$prNumber" 2>$null - if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($prJson)) { - $global:LASTEXITCODE = 0 - Write-Host " ⏭️ PR #$prNumber could not be loaded; skipping stale decision" - continue + $prOutput = @(& gh api "repos/$Owner/$Repo/pulls/$prNumber" 2>&1) + $prExitCode = $LASTEXITCODE + $prJson = ($prOutput | Out-String).Trim() + if ($prExitCode -ne 0) { + if (Test-GhApiPrNotFound -Output $prJson) { + $global:LASTEXITCODE = 0 + Write-Host " ⏭️ PR #$prNumber no longer exists; skipping stale decision" + continue + } + + throw "Failed to load PR #$prNumber via gh api: $(ConvertTo-SafeLogValue $prJson)" + } + if ([string]::IsNullOrWhiteSpace($prJson)) { + throw "Failed to load PR #$prNumber via gh api: empty response." + } + try { + $pr = $prJson | ConvertFrom-Json + } catch { + throw "Failed to parse PR #$prNumber response from gh api: $(ConvertTo-SafeLogValue ([string]$_))" } - $pr = $prJson | ConvertFrom-Json if ($pr.state -ne 'open') { Write-Host " ⏭️ PR #$prNumber is not open ($($pr.state)); skipping" continue diff --git a/.github/scripts/Query-RerunReadyPRs.ps1 b/.github/scripts/Query-RerunReadyPRs.ps1 index 209bcddd0673..224fe3712233 100644 --- a/.github/scripts/Query-RerunReadyPRs.ps1 +++ b/.github/scripts/Query-RerunReadyPRs.ps1 @@ -101,7 +101,8 @@ foreach ($pr in @($searchResult)) { $latestRerun = Get-LatestRerunComment -Comments $activity $reviewOptionAuthors = @(Get-ReviewOptionAuthorLogins -Comments $activity) $reviewOptions = Get-LatestReviewCommandOptions -Comments $activity -AllowedAuthorLogins $reviewOptionAuthors - $authorLogin = if ($pr.author -and $pr.author.login) { [string]$pr.author.login } else { '' } + $rawAuthorLogin = if ($pr.author -and $pr.author.login) { [string]$pr.author.login } else { '' } + $authorLogin = Normalize-GitHubActorLogin $rawAuthorLogin $contextMarkdown = New-RerunContextMarkdown -Comments $activity -Commits $commits -CurrentHeadSha $pr.headRefOid -PRAuthorLogin $authorLogin -CurrentLabels $labels $platform = if ($reviewOptions.Platform) { $reviewOptions.Platform } else { Get-PlatformFromLabels -Labels $labels } $pipelineRef = if ($reviewOptions.PipelineRef) { $reviewOptions.PipelineRef } else { 'main' } diff --git a/.github/scripts/Resolve-RerunEligibility.Tests.ps1 b/.github/scripts/Resolve-RerunEligibility.Tests.ps1 index 8ec61b87788e..249165bc3181 100644 --- a/.github/scripts/Resolve-RerunEligibility.Tests.ps1 +++ b/.github/scripts/Resolve-RerunEligibility.Tests.ps1 @@ -69,6 +69,12 @@ BeforeAll { } Describe 'Resolve-RerunEligibility' { + It 'normalizes app-style GitHub bot author logins' { + Normalize-GitHubActorLogin 'app/dependabot' | Should -Be 'dependabot[bot]' + Normalize-GitHubActorLogin ' dependabot[bot] ' | Should -Be 'dependabot[bot]' + Normalize-GitHubActorLogin '' | Should -Be '' + } + It 'parses review command branch and platform options for reruns' { $parsed = ConvertFrom-ReviewCommand '/review -b feature/regression-check -p ios' @@ -450,4 +456,18 @@ new $context | Should -Match 'fedcba9' $context | Should -Not -Match '\| .*\/review rerun' } + + It 'renders normalized app-style bot authors without counting bot comments as evidence' { + $comments = @( + New-TestComment -Id 1 -Body (New-AISummaryBody) -CreatedAt '2026-05-31T09:00:00Z' -UpdatedAt '2026-05-31T09:30:00Z' -Login 'MauiBot' -Type 'User' + New-TestComment -Id 2 -Body 'Dependabot follow-up.' -CreatedAt '2026-05-31T09:45:00Z' -Login 'dependabot[bot]' -Type 'Bot' + New-TestComment -Id 3 -Body '/review rerun' -CreatedAt '2026-05-31T09:50:00Z' + ) + + $context = New-RerunContextMarkdown -Comments $comments -Commits @() -CurrentHeadSha 'abcdef123' -PRAuthorLogin 'app/dependabot' + + $context | Should -Match 'PR author: dependabot\[bot\]' + $context | Should -Match 'New non-command author comments: 0' + $context | Should -Not -Match 'Dependabot follow-up' + } } diff --git a/.github/scripts/Resolve-RerunEligibility.ps1 b/.github/scripts/Resolve-RerunEligibility.ps1 index 9899214f35e1..55351c68464b 100644 --- a/.github/scripts/Resolve-RerunEligibility.ps1 +++ b/.github/scripts/Resolve-RerunEligibility.ps1 @@ -276,6 +276,21 @@ function Get-LatestReviewedSha { return $matches[0].Groups[1].Value.ToLowerInvariant() } +function Normalize-GitHubActorLogin { + param([string]$Login) + + if ([string]::IsNullOrWhiteSpace($Login)) { + return '' + } + + $trimmed = $Login.Trim() + if ($trimmed -match '^app/([^/\s]+)$') { + return "$($Matches[1])[bot]" + } + + return $trimmed +} + function Test-CommentIsEvidence { param( [Parameter(Mandatory = $true)]$Comment, @@ -292,7 +307,9 @@ function Test-CommentIsEvidence { if (-not $Comment.user -or [string]::IsNullOrWhiteSpace([string]$Comment.user.login)) { return $false } - if (-not ([string]$Comment.user.login).Equals($PRAuthorLogin, [StringComparison]::OrdinalIgnoreCase)) { + $normalizedAuthorLogin = Normalize-GitHubActorLogin $PRAuthorLogin + $normalizedCommentLogin = Normalize-GitHubActorLogin ([string]$Comment.user.login) + if (-not $normalizedCommentLogin.Equals($normalizedAuthorLogin, [StringComparison]::OrdinalIgnoreCase)) { return $false } if (Test-RerunCommand $Comment.body) { @@ -419,6 +436,7 @@ function New-RerunContextMarkdown { $latestSummary = Get-LatestAISummaryComment -Comments $Comments $latestRerun = Get-LatestRerunComment -Comments $Comments $checkpointRerun = if ($latestRerun) { Get-LatestRerunCommentBefore -Comments $Comments -CurrentCommentId ([Int64]$latestRerun.id) } else { $null } + $normalizedPRAuthorLogin = Normalize-GitHubActorLogin $PRAuthorLogin $readyLabelPresent = @($CurrentLabels | Where-Object { $_ -eq $ReadyForRerunLabel }).Count -gt 0 $inProgressLabelPresent = @($CurrentLabels | Where-Object { $_ -eq $ReviewInProgressLabel }).Count -gt 0 @@ -438,7 +456,7 @@ function New-RerunContextMarkdown { $evidenceComments = @() if ($checkpoint) { $evidenceComments = @($Comments | Where-Object { - (Test-CommentIsEvidence -Comment $_ -CurrentCommentId 0 -PRAuthorLogin $PRAuthorLogin) -and + (Test-CommentIsEvidence -Comment $_ -CurrentCommentId 0 -PRAuthorLogin $normalizedPRAuthorLogin) -and (Get-ObjectDate $_ 'created_at') -gt $checkpoint } | Sort-Object @{ Expression = { Get-ObjectDate $_ 'created_at' }; Descending = $false }, @{ Expression = { [Int64]$_.id }; Descending = $false }) } @@ -477,7 +495,7 @@ function New-RerunContextMarkdown { } else { $lines.Add('- Activity checkpoint: none') } - $lines.Add("- PR author: $(if ([string]::IsNullOrWhiteSpace($PRAuthorLogin)) { 'unknown' } else { $PRAuthorLogin })") + $lines.Add("- PR author: $(if ([string]::IsNullOrWhiteSpace($normalizedPRAuthorLogin)) { 'unknown' } else { $normalizedPRAuthorLogin })") $lines.Add("- Latest reviewed SHA: $(if ($latestReviewedSha) { $latestReviewedSha } else { 'unknown' })") $lines.Add("- Current head SHA: $(if ($CurrentHeadSha) { $CurrentHeadSha } else { 'unknown' })") $lines.Add("- Current head differs from latest reviewed SHA: $($headDiffers.ToString().ToLowerInvariant())") @@ -579,7 +597,8 @@ function Resolve-RerunEligibility { return [pscustomobject]@{ Eligible = $true; Reason = 'new-head-commit'; Label = $ReadyForRerunLabel } } - if (Test-HasEvidenceCommentAfter -Comments $Comments -Checkpoint $checkpoint -CurrentCommentId $CurrentCommentId -PRAuthorLogin $PRAuthorLogin) { + $normalizedPRAuthorLogin = Normalize-GitHubActorLogin $PRAuthorLogin + if (Test-HasEvidenceCommentAfter -Comments $Comments -Checkpoint $checkpoint -CurrentCommentId $CurrentCommentId -PRAuthorLogin $normalizedPRAuthorLogin) { $reason = if ($checkpointReason -eq 'previous-rerun') { 'new-author-comment-after-previous-rerun' } else { 'new-author-comment-after-ai-summary' } return [pscustomobject]@{ Eligible = $true; Reason = $reason; Label = $ReadyForRerunLabel } } From f26427350d185c72c4160de3a15664995febbd78 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:30:49 +0200 Subject: [PATCH 6/8] Address rerun scanner safe-output review feedback Keep gh stderr separate from PR JSON, fail the safe-output job when systemic decision-processing errors occur, and clarify the app-style actor normalization test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/Invoke-RerunReviewTrigger.ps1 | 23 +++++++++++++++---- .../Resolve-RerunEligibility.Tests.ps1 | 9 +++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/scripts/Invoke-RerunReviewTrigger.ps1 b/.github/scripts/Invoke-RerunReviewTrigger.ps1 index fb07576475bf..4f7aba653110 100644 --- a/.github/scripts/Invoke-RerunReviewTrigger.ps1 +++ b/.github/scripts/Invoke-RerunReviewTrigger.ps1 @@ -280,6 +280,7 @@ if ($items.Count -eq 0) { exit 0 } $candidates = @(Get-CandidateItems -Path $env:RERUN_CANDIDATES_PATH) +$hadProcessingFailure = $false foreach ($item in $items) { $prNumber = 0 @@ -321,17 +322,24 @@ foreach ($item in $items) { } Write-Host "Processing PR #$prNumber decision=$decision reason=$(ConvertTo-SafeLogValue $reason)" - $prOutput = @(& gh api "repos/$Owner/$Repo/pulls/$prNumber" 2>&1) - $prExitCode = $LASTEXITCODE - $prJson = ($prOutput | Out-String).Trim() + $prStdErrFile = New-TemporaryFile + try { + $prOutput = @(& gh api "repos/$Owner/$Repo/pulls/$prNumber" 2> $prStdErrFile) + $prExitCode = $LASTEXITCODE + $prJson = ($prOutput | Out-String).Trim() + $prStdErr = (Get-Content -Raw -LiteralPath $prStdErrFile -ErrorAction SilentlyContinue).Trim() + } finally { + Remove-Item -LiteralPath $prStdErrFile -Force -ErrorAction SilentlyContinue + } if ($prExitCode -ne 0) { - if (Test-GhApiPrNotFound -Output $prJson) { + $prError = if ([string]::IsNullOrWhiteSpace($prStdErr)) { $prJson } else { $prStdErr } + if (Test-GhApiPrNotFound -Output $prError) { $global:LASTEXITCODE = 0 Write-Host " ⏭️ PR #$prNumber no longer exists; skipping stale decision" continue } - throw "Failed to load PR #$prNumber via gh api: $(ConvertTo-SafeLogValue $prJson)" + throw "Failed to load PR #$prNumber via gh api: $(ConvertTo-SafeLogValue $prError)" } if ([string]::IsNullOrWhiteSpace($prJson)) { throw "Failed to load PR #$prNumber via gh api: empty response." @@ -428,8 +436,13 @@ foreach ($item in $items) { } catch { $target = if ($prNumber -gt 0) { "PR #$prNumber" } else { "agent decision" } Write-Host "::error::Failed to process $target`: $(ConvertTo-SafeLogValue ([string]$_))" + $hadProcessingFailure = $true continue } } +if ($hadProcessingFailure) { + exit 1 +} + exit 0 diff --git a/.github/scripts/Resolve-RerunEligibility.Tests.ps1 b/.github/scripts/Resolve-RerunEligibility.Tests.ps1 index 249165bc3181..340303cb8fcf 100644 --- a/.github/scripts/Resolve-RerunEligibility.Tests.ps1 +++ b/.github/scripts/Resolve-RerunEligibility.Tests.ps1 @@ -96,6 +96,11 @@ Describe 'Resolve-RerunEligibility' { Should -Be 'feature/regression-check' } + It 'normalizes app-style GitHub actor logins to bot logins' { + Normalize-GitHubActorLogin 'app/dependabot' | Should -Be 'dependabot[bot]' + Normalize-GitHubActorLogin 'dev-user' | Should -Be 'dev-user' + } + It 'finds latest normal review command while ignoring rerun and tests commands' { $comments = @( New-TestComment -Id 1 -Body '/review -b old/ref -p android' -CreatedAt '2026-05-31T09:00:00Z' @@ -457,10 +462,9 @@ new $context | Should -Not -Match '\| .*\/review rerun' } - It 'renders normalized app-style bot authors without counting bot comments as evidence' { + It 'renders normalized app-style bot authors in rerun context' { $comments = @( New-TestComment -Id 1 -Body (New-AISummaryBody) -CreatedAt '2026-05-31T09:00:00Z' -UpdatedAt '2026-05-31T09:30:00Z' -Login 'MauiBot' -Type 'User' - New-TestComment -Id 2 -Body 'Dependabot follow-up.' -CreatedAt '2026-05-31T09:45:00Z' -Login 'dependabot[bot]' -Type 'Bot' New-TestComment -Id 3 -Body '/review rerun' -CreatedAt '2026-05-31T09:50:00Z' ) @@ -468,6 +472,5 @@ new $context | Should -Match 'PR author: dependabot\[bot\]' $context | Should -Match 'New non-command author comments: 0' - $context | Should -Not -Match 'Dependabot follow-up' } } From 0e519c1057e02ab7dac4c4386cd05a62e7528b84 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:42:50 +0200 Subject: [PATCH 7/8] Fix rerun scanner PR fetch stderr handling Avoid trimming null stderr on successful gh api calls and make candidate query native command failures fail fast instead of looking like an empty queue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../scripts/Invoke-RerunReviewTrigger.Tests.ps1 | 12 +++++++++++- .github/scripts/Invoke-RerunReviewTrigger.ps1 | 14 ++++++++++++-- .github/scripts/Query-RerunReadyPRs.ps1 | 3 +++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 b/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 index b6b2a0e55a52..7d4554c45ddf 100644 --- a/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 +++ b/.github/scripts/Invoke-RerunReviewTrigger.Tests.ps1 @@ -14,7 +14,7 @@ BeforeAll { $script:ReviewTriggerWindowHours = 24 $script:MaxReviewTriggersPerWindow = 3 - foreach ($functionName in @('Get-ReviewTriggerRateLimitStatus', 'ConvertTo-SafeLogValue', 'Test-GhApiPrNotFound', 'Get-MatchingCandidate', 'Normalize-PipelineRef', 'Get-PlatformFromLabels')) { + foreach ($functionName in @('Get-ReviewTriggerRateLimitStatus', 'ConvertTo-SafeLogValue', 'ConvertTo-TrimmedString', 'Test-GhApiPrNotFound', 'Get-MatchingCandidate', 'Normalize-PipelineRef', 'Get-PlatformFromLabels')) { $function = $ast.Find({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $args[0].Name -eq $functionName @@ -87,6 +87,16 @@ Describe 'Test-GhApiPrNotFound' { } } +Describe 'ConvertTo-TrimmedString' { + It 'returns empty string for null values' { + ConvertTo-TrimmedString $null | Should -Be '' + } + + It 'trims non-null values' { + ConvertTo-TrimmedString " ok`n" | Should -Be 'ok' + } +} + Describe 'Get-MatchingCandidate' { It 'matches only PRs in the deterministic candidate set' { $candidates = @( diff --git a/.github/scripts/Invoke-RerunReviewTrigger.ps1 b/.github/scripts/Invoke-RerunReviewTrigger.ps1 index 4f7aba653110..202bb125fcd0 100644 --- a/.github/scripts/Invoke-RerunReviewTrigger.ps1 +++ b/.github/scripts/Invoke-RerunReviewTrigger.ps1 @@ -78,6 +78,16 @@ function Test-GhApiPrNotFound { return $Output -match '(?i)\bHTTP\s+(404|410)\b' -or $Output -match '(?i)\b(Not Found|Gone)\b' } +function ConvertTo-TrimmedString { + param([AllowNull()][object]$Value) + + if ($null -eq $Value) { + return '' + } + + return ([string]$Value).Trim() +} + function Add-CommentReaction { param( [Parameter(Mandatory = $true)][Int64]$CommentId, @@ -326,8 +336,8 @@ foreach ($item in $items) { try { $prOutput = @(& gh api "repos/$Owner/$Repo/pulls/$prNumber" 2> $prStdErrFile) $prExitCode = $LASTEXITCODE - $prJson = ($prOutput | Out-String).Trim() - $prStdErr = (Get-Content -Raw -LiteralPath $prStdErrFile -ErrorAction SilentlyContinue).Trim() + $prJson = ConvertTo-TrimmedString ($prOutput | Out-String) + $prStdErr = ConvertTo-TrimmedString (Get-Content -Raw -LiteralPath $prStdErrFile -ErrorAction SilentlyContinue) } finally { Remove-Item -LiteralPath $prStdErrFile -Force -ErrorAction SilentlyContinue } diff --git a/.github/scripts/Query-RerunReadyPRs.ps1 b/.github/scripts/Query-RerunReadyPRs.ps1 index 224fe3712233..e71e4c04a33f 100644 --- a/.github/scripts/Query-RerunReadyPRs.ps1 +++ b/.github/scripts/Query-RerunReadyPRs.ps1 @@ -12,6 +12,9 @@ param( ) $ErrorActionPreference = 'Stop' +if ($PSVersionTable.PSVersion -ge [version]'7.3') { + $PSNativeCommandUseErrorActionPreference = $true +} $ReadyForRerunLabel = 's/agent-ready-for-rerun' $ReviewInProgressLabel = 's/agent-review-in-progress' From df5f31480cc9bc56593900c4e84427e63d665a78 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:19:11 +0200 Subject: [PATCH 8/8] Scope rerun candidate-query failure handling to gh pr list Drop the script-wide $PSNativeCommandUseErrorActionPreference that leaked via dynamic scoping into the dot-sourced shared helpers (e.g. Test-AgentReviewInProgressIsStale, Get-IssueLabels) and defeated their 2>$null + $LASTEXITCODE graceful fallbacks, turning a single transient gh failure into a full scanner abort. Instead check $LASTEXITCODE explicitly right after the gh pr list candidate query so a real query failure still fails fast instead of looking like an empty queue, while per-PR helper calls keep degrading gracefully and behave identically regardless of the entry point (Query vs Invoke). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/scripts/Query-RerunReadyPRs.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/scripts/Query-RerunReadyPRs.ps1 b/.github/scripts/Query-RerunReadyPRs.ps1 index e71e4c04a33f..a02258be7e41 100644 --- a/.github/scripts/Query-RerunReadyPRs.ps1 +++ b/.github/scripts/Query-RerunReadyPRs.ps1 @@ -12,9 +12,6 @@ param( ) $ErrorActionPreference = 'Stop' -if ($PSVersionTable.PSVersion -ge [version]'7.3') { - $PSNativeCommandUseErrorActionPreference = $true -} $ReadyForRerunLabel = 's/agent-ready-for-rerun' $ReviewInProgressLabel = 's/agent-review-in-progress' @@ -81,12 +78,16 @@ function Get-PlatformFromLabels { return 'android' } -$searchResult = gh pr list ` +$searchJson = gh pr list ` --repo "$Owner/$Repo" ` --state open ` --label $ReadyForRerunLabel ` --limit $MaxPRs ` - --json number,title,url,headRefOid,isDraft,labels,author | ConvertFrom-Json + --json number,title,url,headRefOid,isDraft,labels,author +if ($LASTEXITCODE -ne 0) { + throw "Failed to list open PRs labeled '$ReadyForRerunLabel' (gh pr list exited with code $LASTEXITCODE)." +} +$searchResult = $searchJson | ConvertFrom-Json $candidates = @() foreach ($pr in @($searchResult)) {