From a6c7664eb8f41386e7d601f74c5c2a0a8b7f61db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:47:31 +0000 Subject: [PATCH 01/14] Initial plan From c10c2ef209d568b71040354ecc7092bcdd76f4d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:51:31 +0000 Subject: [PATCH 02/14] Add workflow to restart old PR status checks Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/workflows/RestartPRStatusCheck.yaml | 112 ++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 .github/workflows/RestartPRStatusCheck.yaml diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml new file mode 100644 index 0000000000..2221f796d2 --- /dev/null +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -0,0 +1,112 @@ +name: 'Restart Pull Request Status Check' + +on: + schedule: + - cron: '0 0 */3 * *' # Run every 3 days at midnight UTC + workflow_dispatch: # Allow manual trigger for testing + +permissions: + actions: write + checks: read + contents: read + pull-requests: read + +defaults: + run: + shell: pwsh + +jobs: + RestartStatusChecks: + runs-on: ubuntu-latest + name: Restart Old PR Status Checks + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Check and Restart Status Checks + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + $ErrorActionPreference = "Stop" + $ProgressPreference = "SilentlyContinue" + Set-StrictMode -Version 2.0 + + Write-Host "Fetching open pull requests..." + + # Get all open pull requests + $prs = gh pr list --state open --json number,headRefName,title,url --limit 1000 | ConvertFrom-Json + + Write-Host "Found $($prs.Count) open pull requests" + + if ($prs.Count -eq 0) { + Write-Host "No open pull requests found, exiting" + exit 0 + } + + $now = [DateTime]::UtcNow + $thresholdHours = 72 + $restarted = 0 + + # Get all workflow runs for pull_request event + Write-Host "Fetching workflow runs..." + $allRuns = gh api --paginate "/repos/${{ github.repository }}/actions/runs?event=pull_request&per_page=100" | ConvertFrom-Json + $prBuildRuns = $allRuns.workflow_runs | Where-Object { $_.name -eq "Pull Request Build" } + + Write-Host "Found $($prBuildRuns.Count) 'Pull Request Build' workflow runs" + + foreach ($pr in $prs) { + Write-Host "" + Write-Host "Checking PR #$($pr.number): $($pr.title)" + + # Filter workflow runs for this specific PR + $prBuilds = $prBuildRuns | Where-Object { + $_.pull_requests | Where-Object { $_.number -eq $pr.number } + } + + if ($prBuilds.Count -eq 0) { + Write-Host " No 'Pull Request Build' workflow runs found for this PR" + continue + } + + # Get the most recent run + $latestRun = $prBuilds | Sort-Object -Property created_at -Descending | Select-Object -First 1 + + Write-Host " Latest workflow run: $($latestRun.id) (Status: $($latestRun.status), Conclusion: $($latestRun.conclusion))" + + # Check if the run is completed and successful + if ($latestRun.status -ne "completed") { + Write-Host " Workflow is still running, skipping" + continue + } + + if ($latestRun.conclusion -ne "success") { + Write-Host " Workflow conclusion is '$($latestRun.conclusion)', not 'success', skipping" + continue + } + + # Check the age of the completion + $completedAt = [DateTime]::Parse($latestRun.updated_at) + $ageInHours = ($now - $completedAt).TotalHours + + Write-Host " Completed at: $completedAt UTC (Age: $([Math]::Round($ageInHours, 2)) hours)" + + if ($ageInHours -gt $thresholdHours) { + Write-Host " Status check is older than $thresholdHours hours, restarting..." + + try { + # Re-run the workflow + gh api --method POST "/repos/${{ github.repository }}/actions/runs/$($latestRun.id)/rerun" | Out-Null + Write-Host " ✓ Successfully triggered re-run of workflow $($latestRun.id)" + $restarted++ + } + catch { + Write-Host " ✗ Failed to restart workflow: $_" + } + } + else { + Write-Host " Status check is recent enough, no action needed" + } + } + + Write-Host "" + Write-Host "Summary: Restarted $restarted workflow run(s)" From 1e77a3f7b938682762c8d973693668fbdbd1d9dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:53:34 +0000 Subject: [PATCH 03/14] Fix null check for pull_requests in workflow Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/workflows/RestartPRStatusCheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml index 2221f796d2..38a40a69dd 100644 --- a/.github/workflows/RestartPRStatusCheck.yaml +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -60,7 +60,7 @@ jobs: # Filter workflow runs for this specific PR $prBuilds = $prBuildRuns | Where-Object { - $_.pull_requests | Where-Object { $_.number -eq $pr.number } + $_.pull_requests -and ($_.pull_requests | Where-Object { $_.number -eq $pr.number }) } if ($prBuilds.Count -eq 0) { From 50ff79e355d474146691e775808558d801c03eef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:55:32 +0000 Subject: [PATCH 04/14] Address code review feedback: improve API efficiency and date parsing Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/workflows/RestartPRStatusCheck.yaml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml index 38a40a69dd..1b3f5a6520 100644 --- a/.github/workflows/RestartPRStatusCheck.yaml +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -48,9 +48,18 @@ jobs: $restarted = 0 # Get all workflow runs for pull_request event - Write-Host "Fetching workflow runs..." - $allRuns = gh api --paginate "/repos/${{ github.repository }}/actions/runs?event=pull_request&per_page=100" | ConvertFrom-Json - $prBuildRuns = $allRuns.workflow_runs | Where-Object { $_.name -eq "Pull Request Build" } + Write-Host "Fetching workflows..." + $workflows = gh api "/repos/${{ github.repository }}/actions/workflows" | ConvertFrom-Json + $prBuildWorkflow = $workflows.workflows | Where-Object { $_.name -eq "Pull Request Build" } + + if (-not $prBuildWorkflow) { + Write-Host "Could not find 'Pull Request Build' workflow, exiting" + exit 0 + } + + Write-Host "Fetching workflow runs for 'Pull Request Build' (ID: $($prBuildWorkflow.id))..." + $allRuns = gh api --paginate "/repos/${{ github.repository }}/actions/workflows/$($prBuildWorkflow.id)/runs?event=pull_request&per_page=100" | ConvertFrom-Json + $prBuildRuns = $allRuns.workflow_runs Write-Host "Found $($prBuildRuns.Count) 'Pull Request Build' workflow runs" @@ -85,7 +94,7 @@ jobs: } # Check the age of the completion - $completedAt = [DateTime]::Parse($latestRun.updated_at) + $completedAt = [DateTime]::Parse($latestRun.updated_at, [System.Globalization.CultureInfo]::InvariantCulture) $ageInHours = ($now - $completedAt).TotalHours Write-Host " Completed at: $completedAt UTC (Age: $([Math]::Round($ageInHours, 2)) hours)" From 13924195baccc571840d8b846d5975bf9145f393 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:25:23 +0000 Subject: [PATCH 05/14] Refactor to use gh pr checks and add retry logic Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/workflows/RestartPRStatusCheck.yaml | 111 ++++++++++++-------- 1 file changed, 68 insertions(+), 43 deletions(-) diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml index 1b3f5a6520..1352533098 100644 --- a/.github/workflows/RestartPRStatusCheck.yaml +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -18,7 +18,7 @@ defaults: jobs: RestartStatusChecks: runs-on: ubuntu-latest - name: Restart Old PR Status Checks + name: Restart Stale PR Status Checks steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -46,70 +46,93 @@ jobs: $now = [DateTime]::UtcNow $thresholdHours = 72 $restarted = 0 - - # Get all workflow runs for pull_request event - Write-Host "Fetching workflows..." - $workflows = gh api "/repos/${{ github.repository }}/actions/workflows" | ConvertFrom-Json - $prBuildWorkflow = $workflows.workflows | Where-Object { $_.name -eq "Pull Request Build" } - - if (-not $prBuildWorkflow) { - Write-Host "Could not find 'Pull Request Build' workflow, exiting" - exit 0 - } - - Write-Host "Fetching workflow runs for 'Pull Request Build' (ID: $($prBuildWorkflow.id))..." - $allRuns = gh api --paginate "/repos/${{ github.repository }}/actions/workflows/$($prBuildWorkflow.id)/runs?event=pull_request&per_page=100" | ConvertFrom-Json - $prBuildRuns = $allRuns.workflow_runs - - Write-Host "Found $($prBuildRuns.Count) 'Pull Request Build' workflow runs" + $failed = 0 + $maxRetries = 3 foreach ($pr in $prs) { Write-Host "" Write-Host "Checking PR #$($pr.number): $($pr.title)" - # Filter workflow runs for this specific PR - $prBuilds = $prBuildRuns | Where-Object { - $_.pull_requests -and ($_.pull_requests | Where-Object { $_.number -eq $pr.number }) + # Get checks for this PR + try { + $checks = gh pr checks $pr.number --json name,status,conclusion,completedAt,detailsUrl | ConvertFrom-Json } - - if ($prBuilds.Count -eq 0) { - Write-Host " No 'Pull Request Build' workflow runs found for this PR" + catch { + Write-Host " ✗ Failed to get checks for PR: $_" + $failed++ continue } - # Get the most recent run - $latestRun = $prBuilds | Sort-Object -Property created_at -Descending | Select-Object -First 1 + # Find the "Pull Request Status Check" + $statusCheck = $checks | Where-Object { $_.name -eq "Pull Request Status Check" } - Write-Host " Latest workflow run: $($latestRun.id) (Status: $($latestRun.status), Conclusion: $($latestRun.conclusion))" + if (-not $statusCheck) { + Write-Host " No 'Pull Request Status Check' found for this PR" + continue + } + + Write-Host " Check status: $($statusCheck.status), Conclusion: $($statusCheck.conclusion)" - # Check if the run is completed and successful - if ($latestRun.status -ne "completed") { - Write-Host " Workflow is still running, skipping" + # Check if the check is completed and successful + if ($statusCheck.status -ne "COMPLETED") { + Write-Host " Check is still running, skipping" continue } - if ($latestRun.conclusion -ne "success") { - Write-Host " Workflow conclusion is '$($latestRun.conclusion)', not 'success', skipping" + if ($statusCheck.conclusion -ne "SUCCESS") { + Write-Host " Check conclusion is '$($statusCheck.conclusion)', not 'SUCCESS', skipping" continue } # Check the age of the completion - $completedAt = [DateTime]::Parse($latestRun.updated_at, [System.Globalization.CultureInfo]::InvariantCulture) + if (-not $statusCheck.completedAt) { + Write-Host " No completion time available, skipping" + continue + } + + $completedAt = [DateTime]::Parse($statusCheck.completedAt, [System.Globalization.CultureInfo]::InvariantCulture) $ageInHours = ($now - $completedAt).TotalHours Write-Host " Completed at: $completedAt UTC (Age: $([Math]::Round($ageInHours, 2)) hours)" if ($ageInHours -gt $thresholdHours) { - Write-Host " Status check is older than $thresholdHours hours, restarting..." - - try { - # Re-run the workflow - gh api --method POST "/repos/${{ github.repository }}/actions/runs/$($latestRun.id)/rerun" | Out-Null - Write-Host " ✓ Successfully triggered re-run of workflow $($latestRun.id)" - $restarted++ - } - catch { - Write-Host " ✗ Failed to restart workflow: $_" + Write-Host " Status check is older than $thresholdHours hours, requesting rerun..." + + # Try to rerequest the check with retries + $success = $false + for ($retry = 0; $retry -lt $maxRetries; $retry++) { + try { + if ($retry -gt 0) { + Write-Host " Retry attempt $($retry + 1)/$maxRetries..." + Start-Sleep -Seconds 2 + } + + # Rerequest the check by re-running the workflow + # First, get the workflow run ID from the check details URL + if ($statusCheck.detailsUrl -match '/runs/(\d+)') { + $runId = $matches[1] + gh api --method POST "/repos/${{ github.repository }}/actions/runs/$runId/rerun" | Out-Null + Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" + $restarted++ + $success = $true + break + } + else { + Write-Host " ✗ Could not extract run ID from details URL: $($statusCheck.detailsUrl)" + $failed++ + break + } + } + catch { + $errorMsg = $_.Exception.Message + if ($retry -eq $maxRetries - 1) { + Write-Host " ✗ Failed to restart workflow after $maxRetries attempts: $errorMsg" + $failed++ + } + else { + Write-Host " ⚠ Attempt $($retry + 1) failed: $errorMsg" + } + } } } else { @@ -118,4 +141,6 @@ jobs: } Write-Host "" - Write-Host "Summary: Restarted $restarted workflow run(s)" + Write-Host "Summary:" + Write-Host " ✓ Successfully restarted: $restarted workflow run(s)" + Write-Host " ✗ Failed attempts: $failed" From 4cc39a1488a1a58ed70323cedcfb3cd93c831f6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:27:10 +0000 Subject: [PATCH 06/14] Add exponential backoff and run ID validation Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/workflows/RestartPRStatusCheck.yaml | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml index 1352533098..58c417ad2a 100644 --- a/.github/workflows/RestartPRStatusCheck.yaml +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -103,19 +103,29 @@ jobs: for ($retry = 0; $retry -lt $maxRetries; $retry++) { try { if ($retry -gt 0) { - Write-Host " Retry attempt $($retry + 1)/$maxRetries..." - Start-Sleep -Seconds 2 + # Exponential backoff: 2s, 4s, 8s + $delaySeconds = 2 * [Math]::Pow(2, $retry - 1) + Write-Host " Retry attempt $($retry + 1)/$maxRetries (waiting $delaySeconds seconds)..." + Start-Sleep -Seconds $delaySeconds } # Rerequest the check by re-running the workflow # First, get the workflow run ID from the check details URL if ($statusCheck.detailsUrl -match '/runs/(\d+)') { $runId = $matches[1] - gh api --method POST "/repos/${{ github.repository }}/actions/runs/$runId/rerun" | Out-Null - Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" - $restarted++ - $success = $true - break + # Validate run ID is a positive integer + if ([int]$runId -gt 0) { + gh api --method POST "/repos/${{ github.repository }}/actions/runs/$runId/rerun" | Out-Null + Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" + $restarted++ + $success = $true + break + } + else { + Write-Host " ✗ Invalid run ID extracted: $runId" + $failed++ + break + } } else { Write-Host " ✗ Could not extract run ID from details URL: $($statusCheck.detailsUrl)" From 4431d75d4f5f616076563e02ca342c14ee56c663 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:52:47 +0000 Subject: [PATCH 07/14] Extract script to action and address review comments Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .../actions/RestartStalePRChecks/action.ps1 | 132 +++++++++++++++++ .../actions/RestartStalePRChecks/action.yaml | 32 ++++ .github/workflows/RestartPRStatusCheck.yaml | 137 +----------------- 3 files changed, 170 insertions(+), 131 deletions(-) create mode 100644 .github/actions/RestartStalePRChecks/action.ps1 create mode 100644 .github/actions/RestartStalePRChecks/action.yaml diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 new file mode 100644 index 0000000000..4ca27d1ade --- /dev/null +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -0,0 +1,132 @@ +param ( + [Parameter(Mandatory = $false, HelpMessage="Threshold in hours for considering a check stale")] + [int] $thresholdHours = 72, + [Parameter(Mandatory = $false, HelpMessage="Maximum number of retry attempts")] + [int] $maxRetries = 3 +) + +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" +Set-StrictMode -Version 2.0 + +Write-Host "Fetching open pull requests..." + +# Get all open pull requests +$prs = gh pr list --state open --json number,headRefName,title,url --limit 1000 | ConvertFrom-Json + +Write-Host "Found $($prs.Count) open pull requests" + +if ($prs.Count -eq 0) { + Write-Host "::notice::No open pull requests found" + exit 0 +} + +$now = [DateTime]::UtcNow +$restarted = 0 +$failed = 0 + +foreach ($pr in $prs) { + Write-Host "" + Write-Host "Checking PR #$($pr.number): $($pr.title)" + + # Get checks for this PR + try { + $checks = gh pr checks $pr.number --json name,state,bucket,completedAt,link | ConvertFrom-Json + } + catch { + Write-Host " ✗ Failed to get checks for PR: $_" + $failed++ + continue + } + + # Find the "Pull Request Status Check" + $statusCheck = $checks | Where-Object { $_.name -eq "Pull Request Status Check" } + + if (-not $statusCheck) { + Write-Host " No 'Pull Request Status Check' found for this PR" + continue + } + + Write-Host " Check state: $($statusCheck.state), Bucket: $($statusCheck.bucket)" + + # Check if the check is completed and successful + if ($statusCheck.state -ne "SUCCESS") { + Write-Host " Check state is '$($statusCheck.state)', not 'SUCCESS', skipping" + continue + } + + if ($statusCheck.bucket -ne "pass") { + Write-Host " Check bucket is '$($statusCheck.bucket)', not 'pass', skipping" + continue + } + + $completedAt = [DateTime]::Parse($statusCheck.completedAt, [System.Globalization.CultureInfo]::InvariantCulture) + $ageInHours = ($now - $completedAt).TotalHours + + Write-Host " Completed at: $completedAt UTC (Age: $([Math]::Round($ageInHours, 2)) hours)" + + if ($ageInHours -le $thresholdHours) { + Write-Host " Status check is recent enough, no action needed" + continue + } + + Write-Host " Status check is older than $thresholdHours hours, requesting rerun..." + + # Try to rerequest the check with retries + $success = $false + for ($retry = 0; $retry -lt $maxRetries; $retry++) { + try { + if ($retry -gt 0) { + # Exponential backoff: 2s, 4s, 8s + $delaySeconds = 2 * [Math]::Pow(2, $retry - 1) + Write-Host " Retry attempt $($retry + 1)/$maxRetries (waiting $delaySeconds seconds)..." + Start-Sleep -Seconds $delaySeconds + } + + # Rerequest the check by re-running the workflow + # First, get the workflow run ID from the check link + if ($statusCheck.link -match '/runs/(\d+)') { + $runId = $matches[1] + # Validate run ID is a positive integer + if ([int]$runId -gt 0) { + gh api --method POST "/repos/$env:GITHUB_REPOSITORY/actions/runs/$runId/rerun" | Out-Null + Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" + $restarted++ + $success = $true + break + } + else { + Write-Host " ✗ Invalid run ID extracted: $runId" + $failed++ + break + } + } + else { + Write-Host " ✗ Could not extract run ID from link: $($statusCheck.link)" + $failed++ + break + } + } + catch { + $errorMsg = $_.Exception.Message + if ($retry -eq $maxRetries - 1) { + Write-Host " ✗ Failed to restart workflow after $maxRetries attempts: $errorMsg" + $failed++ + } + else { + Write-Host " ⚠ Attempt $($retry + 1) failed: $errorMsg" + } + } + } +} + +Write-Host "" +Write-Host "Summary:" +Write-Host " ✓ Successfully restarted: $restarted workflow run(s)" +Write-Host " ✗ Failed attempts: $failed" + +# Add GitHub Actions job summary +Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "## PR Status Check Restart Summary" +Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" +Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ✓ Successfully restarted: **$restarted** workflow run(s)" +Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ✗ Failed attempts: **$failed**" diff --git a/.github/actions/RestartStalePRChecks/action.yaml b/.github/actions/RestartStalePRChecks/action.yaml new file mode 100644 index 0000000000..3c1500e23e --- /dev/null +++ b/.github/actions/RestartStalePRChecks/action.yaml @@ -0,0 +1,32 @@ +name: Restart Stale PR Checks +author: Microsoft Corporation +description: Restart stale PR status checks that are older than a specified threshold +inputs: + thresholdHours: + description: Threshold in hours for considering a check stale + required: false + default: '72' + maxRetries: + description: Maximum number of retry attempts + required: false + default: '3' + token: + description: The GitHub token running the action + required: false + default: ${{ github.token }} +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Restart Stale PR Checks + shell: pwsh + env: + GH_TOKEN: ${{ inputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + ${{ github.action_path }}/action.ps1 -thresholdHours ${{ inputs.thresholdHours }} -maxRetries ${{ inputs.maxRetries }} +branding: + icon: rotate-cw + color: blue diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml index 58c417ad2a..061516a580 100644 --- a/.github/workflows/RestartPRStatusCheck.yaml +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -23,134 +23,9 @@ jobs: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Check and Restart Status Checks - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - $ErrorActionPreference = "Stop" - $ProgressPreference = "SilentlyContinue" - Set-StrictMode -Version 2.0 - - Write-Host "Fetching open pull requests..." - - # Get all open pull requests - $prs = gh pr list --state open --json number,headRefName,title,url --limit 1000 | ConvertFrom-Json - - Write-Host "Found $($prs.Count) open pull requests" - - if ($prs.Count -eq 0) { - Write-Host "No open pull requests found, exiting" - exit 0 - } - - $now = [DateTime]::UtcNow - $thresholdHours = 72 - $restarted = 0 - $failed = 0 - $maxRetries = 3 - - foreach ($pr in $prs) { - Write-Host "" - Write-Host "Checking PR #$($pr.number): $($pr.title)" - - # Get checks for this PR - try { - $checks = gh pr checks $pr.number --json name,status,conclusion,completedAt,detailsUrl | ConvertFrom-Json - } - catch { - Write-Host " ✗ Failed to get checks for PR: $_" - $failed++ - continue - } - - # Find the "Pull Request Status Check" - $statusCheck = $checks | Where-Object { $_.name -eq "Pull Request Status Check" } - - if (-not $statusCheck) { - Write-Host " No 'Pull Request Status Check' found for this PR" - continue - } - - Write-Host " Check status: $($statusCheck.status), Conclusion: $($statusCheck.conclusion)" - - # Check if the check is completed and successful - if ($statusCheck.status -ne "COMPLETED") { - Write-Host " Check is still running, skipping" - continue - } - - if ($statusCheck.conclusion -ne "SUCCESS") { - Write-Host " Check conclusion is '$($statusCheck.conclusion)', not 'SUCCESS', skipping" - continue - } - - # Check the age of the completion - if (-not $statusCheck.completedAt) { - Write-Host " No completion time available, skipping" - continue - } - - $completedAt = [DateTime]::Parse($statusCheck.completedAt, [System.Globalization.CultureInfo]::InvariantCulture) - $ageInHours = ($now - $completedAt).TotalHours - - Write-Host " Completed at: $completedAt UTC (Age: $([Math]::Round($ageInHours, 2)) hours)" - - if ($ageInHours -gt $thresholdHours) { - Write-Host " Status check is older than $thresholdHours hours, requesting rerun..." - - # Try to rerequest the check with retries - $success = $false - for ($retry = 0; $retry -lt $maxRetries; $retry++) { - try { - if ($retry -gt 0) { - # Exponential backoff: 2s, 4s, 8s - $delaySeconds = 2 * [Math]::Pow(2, $retry - 1) - Write-Host " Retry attempt $($retry + 1)/$maxRetries (waiting $delaySeconds seconds)..." - Start-Sleep -Seconds $delaySeconds - } - - # Rerequest the check by re-running the workflow - # First, get the workflow run ID from the check details URL - if ($statusCheck.detailsUrl -match '/runs/(\d+)') { - $runId = $matches[1] - # Validate run ID is a positive integer - if ([int]$runId -gt 0) { - gh api --method POST "/repos/${{ github.repository }}/actions/runs/$runId/rerun" | Out-Null - Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" - $restarted++ - $success = $true - break - } - else { - Write-Host " ✗ Invalid run ID extracted: $runId" - $failed++ - break - } - } - else { - Write-Host " ✗ Could not extract run ID from details URL: $($statusCheck.detailsUrl)" - $failed++ - break - } - } - catch { - $errorMsg = $_.Exception.Message - if ($retry -eq $maxRetries - 1) { - Write-Host " ✗ Failed to restart workflow after $maxRetries attempts: $errorMsg" - $failed++ - } - else { - Write-Host " ⚠ Attempt $($retry + 1) failed: $errorMsg" - } - } - } - } - else { - Write-Host " Status check is recent enough, no action needed" - } - } - - Write-Host "" - Write-Host "Summary:" - Write-Host " ✓ Successfully restarted: $restarted workflow run(s)" - Write-Host " ✗ Failed attempts: $failed" + - name: Restart Stale PR Status Checks + uses: ./.github/actions/RestartStalePRChecks + with: + thresholdHours: 72 + maxRetries: 3 + token: ${{ github.token }} From 49ff744bdb82574d374f8424a4ddbd0175a4b6fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:57:38 +0000 Subject: [PATCH 08/14] Use gh run rerun instead of API call Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/actions/RestartStalePRChecks/action.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 index 4ca27d1ade..65dfc04dc7 100644 --- a/.github/actions/RestartStalePRChecks/action.ps1 +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -89,7 +89,7 @@ foreach ($pr in $prs) { $runId = $matches[1] # Validate run ID is a positive integer if ([int]$runId -gt 0) { - gh api --method POST "/repos/$env:GITHUB_REPOSITORY/actions/runs/$runId/rerun" | Out-Null + gh run rerun $runId -R $env:GITHUB_REPOSITORY | Out-Null Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" $restarted++ $success = $true From 9cfd852f3be457325d60dadf44c3b4bfa0b0d334 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 11:01:00 +0000 Subject: [PATCH 09/14] Remove redundant bucket check and unused headRefName property Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/actions/RestartStalePRChecks/action.ps1 | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 index 65dfc04dc7..9efead0e9e 100644 --- a/.github/actions/RestartStalePRChecks/action.ps1 +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -12,7 +12,7 @@ Set-StrictMode -Version 2.0 Write-Host "Fetching open pull requests..." # Get all open pull requests -$prs = gh pr list --state open --json number,headRefName,title,url --limit 1000 | ConvertFrom-Json +$prs = gh pr list --state open --json number,title,url --limit 1000 | ConvertFrom-Json Write-Host "Found $($prs.Count) open pull requests" @@ -47,7 +47,7 @@ foreach ($pr in $prs) { continue } - Write-Host " Check state: $($statusCheck.state), Bucket: $($statusCheck.bucket)" + Write-Host " Check state: $($statusCheck.state)" # Check if the check is completed and successful if ($statusCheck.state -ne "SUCCESS") { @@ -55,11 +55,6 @@ foreach ($pr in $prs) { continue } - if ($statusCheck.bucket -ne "pass") { - Write-Host " Check bucket is '$($statusCheck.bucket)', not 'pass', skipping" - continue - } - $completedAt = [DateTime]::Parse($statusCheck.completedAt, [System.Globalization.CultureInfo]::InvariantCulture) $ageInHours = ($now - $completedAt).TotalHours From 60eddd1ef1b4d010b3647a3f22fb5bb22105f0d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:25:08 +0000 Subject: [PATCH 10/14] Remove unused success variable and fail on processing errors Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/actions/RestartStalePRChecks/action.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 index 9efead0e9e..4715580f94 100644 --- a/.github/actions/RestartStalePRChecks/action.ps1 +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -68,7 +68,6 @@ foreach ($pr in $prs) { Write-Host " Status check is older than $thresholdHours hours, requesting rerun..." # Try to rerequest the check with retries - $success = $false for ($retry = 0; $retry -lt $maxRetries; $retry++) { try { if ($retry -gt 0) { @@ -87,7 +86,6 @@ foreach ($pr in $prs) { gh run rerun $runId -R $env:GITHUB_REPOSITORY | Out-Null Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" $restarted++ - $success = $true break } else { @@ -125,3 +123,9 @@ Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "## PR Status Check Restart Su Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ✓ Successfully restarted: **$restarted** workflow run(s)" Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ✗ Failed attempts: **$failed**" + +# Exit with error if there were any failures +if ($failed -gt 0) { + Write-Host "::error::Failed to process $failed PR(s)" + exit 1 +} From bffa485c51d14ae46c41a9b66fe6b8dd478a425b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:35:16 +0000 Subject: [PATCH 11/14] Count failed PRs instead of failed attempts Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/actions/RestartStalePRChecks/action.ps1 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 index 4715580f94..c07ad71a5b 100644 --- a/.github/actions/RestartStalePRChecks/action.ps1 +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -68,6 +68,7 @@ foreach ($pr in $prs) { Write-Host " Status check is older than $thresholdHours hours, requesting rerun..." # Try to rerequest the check with retries + $prFailed = $false for ($retry = 0; $retry -lt $maxRetries; $retry++) { try { if ($retry -gt 0) { @@ -90,13 +91,13 @@ foreach ($pr in $prs) { } else { Write-Host " ✗ Invalid run ID extracted: $runId" - $failed++ + $prFailed = $true break } } else { Write-Host " ✗ Could not extract run ID from link: $($statusCheck.link)" - $failed++ + $prFailed = $true break } } @@ -104,13 +105,18 @@ foreach ($pr in $prs) { $errorMsg = $_.Exception.Message if ($retry -eq $maxRetries - 1) { Write-Host " ✗ Failed to restart workflow after $maxRetries attempts: $errorMsg" - $failed++ + $prFailed = $true } else { Write-Host " ⚠ Attempt $($retry + 1) failed: $errorMsg" } } } + + # Increment failed counter once per PR if any attempt failed + if ($prFailed) { + $failed++ + } } Write-Host "" From 557c998e82125cd34ba6874dbbb11d58ccd7c08a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:34:03 +0000 Subject: [PATCH 12/14] Use Invoke-CommandWithRetry, filter mergeable PRs, remove default params Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .../actions/RestartStalePRChecks/action.ps1 | 75 +++++++++---------- .github/workflows/RestartPRStatusCheck.yaml | 2 - 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 index c07ad71a5b..8679331b1a 100644 --- a/.github/actions/RestartStalePRChecks/action.ps1 +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -9,10 +9,13 @@ $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" Set-StrictMode -Version 2.0 +# Import EnlistmentHelperFunctions module +Import-Module "$PSScriptRoot\..\..\..\build\scripts\EnlistmentHelperFunctions.psm1" -DisableNameChecking + Write-Host "Fetching open pull requests..." -# Get all open pull requests -$prs = gh pr list --state open --json number,title,url --limit 1000 | ConvertFrom-Json +# Get all open pull requests with mergeable state +$prs = gh pr list --state open --json number,title,url,mergeable --limit 1000 | ConvertFrom-Json Write-Host "Found $($prs.Count) open pull requests" @@ -29,9 +32,18 @@ foreach ($pr in $prs) { Write-Host "" Write-Host "Checking PR #$($pr.number): $($pr.title)" - # Get checks for this PR + # Check if PR is mergeable + if ($pr.mergeable -ne "MERGEABLE") { + Write-Host " PR is not in MERGEABLE state (current: $($pr.mergeable)), skipping" + continue + } + + # Get checks for this PR with retry + $checks = $null try { - $checks = gh pr checks $pr.number --json name,state,bucket,completedAt,link | ConvertFrom-Json + $checks = Invoke-CommandWithRetry -ScriptBlock { + gh pr checks $pr.number --json name,state,bucket,completedAt,link | ConvertFrom-Json + } -RetryCount $maxRetries -FirstDelay 2 -MaxWaitBetweenRetries 8 } catch { Write-Host " ✗ Failed to get checks for PR: $_" @@ -67,51 +79,34 @@ foreach ($pr in $prs) { Write-Host " Status check is older than $thresholdHours hours, requesting rerun..." - # Try to rerequest the check with retries + # Try to rerequest the check with retries using Invoke-CommandWithRetry $prFailed = $false - for ($retry = 0; $retry -lt $maxRetries; $retry++) { - try { - if ($retry -gt 0) { - # Exponential backoff: 2s, 4s, 8s - $delaySeconds = 2 * [Math]::Pow(2, $retry - 1) - Write-Host " Retry attempt $($retry + 1)/$maxRetries (waiting $delaySeconds seconds)..." - Start-Sleep -Seconds $delaySeconds - } - - # Rerequest the check by re-running the workflow - # First, get the workflow run ID from the check link - if ($statusCheck.link -match '/runs/(\d+)') { - $runId = $matches[1] - # Validate run ID is a positive integer - if ([int]$runId -gt 0) { + try { + # Extract run ID from the check link + if ($statusCheck.link -match '/runs/(\d+)') { + $runId = $matches[1] + # Validate run ID is a positive integer + if ([int]$runId -gt 0) { + Invoke-CommandWithRetry -ScriptBlock { gh run rerun $runId -R $env:GITHUB_REPOSITORY | Out-Null - Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" - $restarted++ - break - } - else { - Write-Host " ✗ Invalid run ID extracted: $runId" - $prFailed = $true - break - } + } -RetryCount $maxRetries -FirstDelay 2 -MaxWaitBetweenRetries 8 + Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" + $restarted++ } else { - Write-Host " ✗ Could not extract run ID from link: $($statusCheck.link)" + Write-Host " ✗ Invalid run ID extracted: $runId" $prFailed = $true - break } } - catch { - $errorMsg = $_.Exception.Message - if ($retry -eq $maxRetries - 1) { - Write-Host " ✗ Failed to restart workflow after $maxRetries attempts: $errorMsg" - $prFailed = $true - } - else { - Write-Host " ⚠ Attempt $($retry + 1) failed: $errorMsg" - } + else { + Write-Host " ✗ Could not extract run ID from link: $($statusCheck.link)" + $prFailed = $true } } + catch { + Write-Host " ✗ Failed to restart workflow: $_" + $prFailed = $true + } # Increment failed counter once per PR if any attempt failed if ($prFailed) { diff --git a/.github/workflows/RestartPRStatusCheck.yaml b/.github/workflows/RestartPRStatusCheck.yaml index 061516a580..c0979b1a7c 100644 --- a/.github/workflows/RestartPRStatusCheck.yaml +++ b/.github/workflows/RestartPRStatusCheck.yaml @@ -26,6 +26,4 @@ jobs: - name: Restart Stale PR Status Checks uses: ./.github/actions/RestartStalePRChecks with: - thresholdHours: 72 - maxRetries: 3 token: ${{ github.token }} From 97e67bf8ecfb07e2693d567ad7d1e6b46e88196e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:05:14 +0000 Subject: [PATCH 13/14] Add WhatIf parameter for testing without triggering reruns Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .../actions/RestartStalePRChecks/action.ps1 | 35 ++++++++++++++----- .../actions/RestartStalePRChecks/action.yaml | 7 +++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/actions/RestartStalePRChecks/action.ps1 b/.github/actions/RestartStalePRChecks/action.ps1 index 8679331b1a..a1fcdd8d37 100644 --- a/.github/actions/RestartStalePRChecks/action.ps1 +++ b/.github/actions/RestartStalePRChecks/action.ps1 @@ -2,7 +2,9 @@ param ( [Parameter(Mandatory = $false, HelpMessage="Threshold in hours for considering a check stale")] [int] $thresholdHours = 72, [Parameter(Mandatory = $false, HelpMessage="Maximum number of retry attempts")] - [int] $maxRetries = 3 + [int] $maxRetries = 3, + [Parameter(Mandatory = $false, HelpMessage="If specified, only performs read operations without triggering any reruns")] + [switch] $WhatIf ) $ErrorActionPreference = "Stop" @@ -12,6 +14,10 @@ Set-StrictMode -Version 2.0 # Import EnlistmentHelperFunctions module Import-Module "$PSScriptRoot\..\..\..\build\scripts\EnlistmentHelperFunctions.psm1" -DisableNameChecking +if ($WhatIf) { + Write-Host "::notice::Running in WhatIf mode - no workflows will be rerun" +} + Write-Host "Fetching open pull requests..." # Get all open pull requests with mergeable state @@ -87,11 +93,17 @@ foreach ($pr in $prs) { $runId = $matches[1] # Validate run ID is a positive integer if ([int]$runId -gt 0) { - Invoke-CommandWithRetry -ScriptBlock { - gh run rerun $runId -R $env:GITHUB_REPOSITORY | Out-Null - } -RetryCount $maxRetries -FirstDelay 2 -MaxWaitBetweenRetries 8 - Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" - $restarted++ + if ($WhatIf) { + Write-Host " [WhatIf] Would trigger re-run of workflow (run ID: $runId)" + $restarted++ + } + else { + Invoke-CommandWithRetry -ScriptBlock { + gh run rerun $runId -R $env:GITHUB_REPOSITORY | Out-Null + } -RetryCount $maxRetries -FirstDelay 2 -MaxWaitBetweenRetries 8 + Write-Host " ✓ Successfully triggered re-run of workflow (run ID: $runId)" + $restarted++ + } } else { Write-Host " ✗ Invalid run ID extracted: $runId" @@ -120,13 +132,18 @@ Write-Host " ✓ Successfully restarted: $restarted workflow run(s)" Write-Host " ✗ Failed attempts: $failed" # Add GitHub Actions job summary -Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "## PR Status Check Restart Summary" +$summaryTitle = if ($WhatIf) { "## PR Status Check Restart Summary (WhatIf Mode)" } else { "## PR Status Check Restart Summary" } +Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summaryTitle Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" +if ($WhatIf) { + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ℹ️ Running in **WhatIf mode** - no workflows were actually rerun" + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" +} Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ✓ Successfully restarted: **$restarted** workflow run(s)" Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- ✗ Failed attempts: **$failed**" -# Exit with error if there were any failures -if ($failed -gt 0) { +# Exit with error if there were any failures (not in WhatIf mode) +if ($failed -gt 0 -and -not $WhatIf) { Write-Host "::error::Failed to process $failed PR(s)" exit 1 } diff --git a/.github/actions/RestartStalePRChecks/action.yaml b/.github/actions/RestartStalePRChecks/action.yaml index 3c1500e23e..174ed317e1 100644 --- a/.github/actions/RestartStalePRChecks/action.yaml +++ b/.github/actions/RestartStalePRChecks/action.yaml @@ -10,6 +10,10 @@ inputs: description: Maximum number of retry attempts required: false default: '3' + whatIf: + description: If true, only performs read operations without triggering any reruns (for testing) + required: false + default: 'false' token: description: The GitHub token running the action required: false @@ -26,7 +30,8 @@ runs: GH_TOKEN: ${{ inputs.token }} GITHUB_REPOSITORY: ${{ github.repository }} run: | - ${{ github.action_path }}/action.ps1 -thresholdHours ${{ inputs.thresholdHours }} -maxRetries ${{ inputs.maxRetries }} + $whatIfSwitch = if ('${{ inputs.whatIf }}' -eq 'true') { '-WhatIf' } else { '' } + & ${{ github.action_path }}/action.ps1 -thresholdHours ${{ inputs.thresholdHours }} -maxRetries ${{ inputs.maxRetries }} $whatIfSwitch branding: icon: rotate-cw color: blue From 327b978949cfe14c61527dd64eef1d8d700fb455 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:12:33 +0000 Subject: [PATCH 14/14] Remove WhatIf from action.yaml, keep in script only Co-authored-by: mazhelez <43066499+mazhelez@users.noreply.github.com> --- .github/actions/RestartStalePRChecks/action.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/actions/RestartStalePRChecks/action.yaml b/.github/actions/RestartStalePRChecks/action.yaml index 174ed317e1..3c1500e23e 100644 --- a/.github/actions/RestartStalePRChecks/action.yaml +++ b/.github/actions/RestartStalePRChecks/action.yaml @@ -10,10 +10,6 @@ inputs: description: Maximum number of retry attempts required: false default: '3' - whatIf: - description: If true, only performs read operations without triggering any reruns (for testing) - required: false - default: 'false' token: description: The GitHub token running the action required: false @@ -30,8 +26,7 @@ runs: GH_TOKEN: ${{ inputs.token }} GITHUB_REPOSITORY: ${{ github.repository }} run: | - $whatIfSwitch = if ('${{ inputs.whatIf }}' -eq 'true') { '-WhatIf' } else { '' } - & ${{ github.action_path }}/action.ps1 -thresholdHours ${{ inputs.thresholdHours }} -maxRetries ${{ inputs.maxRetries }} $whatIfSwitch + ${{ github.action_path }}/action.ps1 -thresholdHours ${{ inputs.thresholdHours }} -maxRetries ${{ inputs.maxRetries }} branding: icon: rotate-cw color: blue