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)) {