diff --git a/.github/skills/azdo-helix-failures/SKILL.md b/.github/skills/azdo-helix-failures/SKILL.md index d12c199dbdabb3..3387017a3f3739 100644 --- a/.github/skills/azdo-helix-failures/SKILL.md +++ b/.github/skills/azdo-helix-failures/SKILL.md @@ -1,19 +1,21 @@ --- name: azdo-helix-failures -description: Retrieve and analyze test failures from Azure DevOps builds and Helix test runs for dotnet repositories. Use when investigating CI failures, debugging failing PRs, or given URLs containing dev.azure.com or helix.dot.net. +description: Analyze CI build and test status from Azure DevOps and Helix for dotnet repository PRs. Use when checking CI status, investigating failures, determining if a PR is ready to merge, or given URLs containing dev.azure.com or helix.dot.net. --- -# Azure DevOps and Helix Failure Analysis +# Azure DevOps and Helix CI Analysis -Analyze CI test failures in Azure DevOps and Helix for dotnet repositories (runtime, sdk, aspnetcore, roslyn, and more). +Analyze CI build status and test failures in Azure DevOps and Helix for dotnet repositories (runtime, sdk, aspnetcore, roslyn, and more). ## When to Use This Skill Use this skill when: +- Checking CI status on a PR ("is CI passing?", "what's the build status?") - Investigating CI failures or checking why a PR's tests are failing +- Determining if a PR is ready to merge based on CI results - Debugging Helix test issues or analyzing build errors - Given URLs containing `dev.azure.com`, `helix.dot.net`, or GitHub PR links with failing checks -- Asked questions like "why is this PR failing", "analyze the CI failures", or "what's wrong with this build" +- Asked questions like "why is this PR failing", "analyze the CI", "is CI green", or "what's the build status" ## Quick Start diff --git a/.github/skills/azdo-helix-failures/references/helix-artifacts.md b/.github/skills/azdo-helix-failures/references/helix-artifacts.md index d2d84de4c88533..245b91226def02 100644 --- a/.github/skills/azdo-helix-failures/references/helix-artifacts.md +++ b/.github/skills/azdo-helix-failures/references/helix-artifacts.md @@ -47,6 +47,10 @@ Always query the specific work item to see what's available rather than assuming Artifacts may be at the root level or nested in subdirectories like `xharness-output/logs/`. +> **Note:** The Helix API has a known bug ([dotnet/dnceng#6072](https://github.com/dotnet/dnceng/issues/6072)) where +> file URIs for subdirectory files are incorrect. The script works around this by rebuilding URIs from the `FileName` +> field, but raw API responses may return broken links for files like `xharness-output/wasm-console.log`. + ## Binlog Files Binlogs are **only present for tests that invoke MSBuild** (build/publish tests, AOT compilation). Standard unit tests don't produce binlogs. diff --git a/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 b/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 index 26e610a359d730..7292738d508eb5 100644 --- a/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 +++ b/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 @@ -354,6 +354,11 @@ function Get-AzDOBuildIdFromPR { # Use gh cli to get the checks with splatted arguments $checksOutput = & gh pr checks $PR --repo $Repository 2>&1 + $ghExitCode = $LASTEXITCODE + + if ($ghExitCode -ne 0 -and -not ($checksOutput | Select-String -Pattern "buildId=")) { + throw "Failed to fetch CI status for PR #$PR in $Repository — check PR number and permissions" + } # Find ALL failing Azure DevOps builds $failingBuilds = @{} @@ -381,7 +386,7 @@ function Get-AzDOBuildIdFromPR { } } } - throw "Could not find Azure DevOps build for PR #$PR in $Repository" + throw "No CI build found for PR #$PR in $Repository — the CI pipeline has not been triggered yet" } # Return all unique failing build IDs @@ -1353,6 +1358,20 @@ function Get-HelixWorkItemDetails { try { $response = Invoke-CachedRestMethod -Uri $url -TimeoutSec $TimeoutSec -AsJson + + # Workaround for https://github.com/dotnet/dnceng/issues/6072: + # Helix API returns incorrect file URIs for files in subdirectories + # (e.g., xharness-output/testResults.xml). Rebuild URI from FileName. + if ($response -and $response.Files) { + $encodedWorkItem = [uri]::EscapeDataString($WorkItemName) + foreach ($file in $response.Files) { + if ($file.FileName -and $file.FileName -match '/') { + $encodedFileName = ($file.FileName -split '/' | ForEach-Object { [uri]::EscapeDataString($_) }) -join '/' + $file.Uri = "https://helix.dot.net/api/jobs/$JobId/workitems/$encodedWorkItem/files/$encodedFileName?api-version=2019-06-17" + } + } + } + return $response } catch { @@ -2012,8 +2031,39 @@ try { $totalFailedJobs += $failedJobs.Count $totalLocalFailures += $localTestFailures.Count + # Compute job summary from timeline + $allJobs = @() + $succeededJobs = 0 + $pendingJobs = 0 + $canceledJobCount = 0 + $skippedJobs = 0 + $warningJobs = 0 + if ($timeline -and $timeline.records) { + $allJobs = @($timeline.records | Where-Object { $_.type -eq "Job" }) + $succeededJobs = @($allJobs | Where-Object { $_.result -eq "succeeded" }).Count + $warningJobs = @($allJobs | Where-Object { $_.result -eq "succeededWithIssues" }).Count + $pendingJobs = @($allJobs | Where-Object { -not $_.result -or $_.state -eq "pending" -or $_.state -eq "inProgress" }).Count + $canceledJobCount = @($allJobs | Where-Object { $_.result -eq "canceled" }).Count + $skippedJobs = @($allJobs | Where-Object { $_.result -eq "skipped" }).Count + } + Write-Host "`n=== Build $currentBuildId Summary ===" -ForegroundColor Yellow - Write-Host "Failed jobs: $($failedJobs.Count)" -ForegroundColor Red + if ($allJobs.Count -gt 0) { + $parts = @() + if ($succeededJobs -gt 0) { $parts += "$succeededJobs passed" } + if ($warningJobs -gt 0) { $parts += "$warningJobs passed with warnings" } + if ($failedJobs.Count -gt 0) { $parts += "$($failedJobs.Count) failed" } + if ($canceledJobCount -gt 0) { $parts += "$canceledJobCount canceled" } + if ($skippedJobs -gt 0) { $parts += "$skippedJobs skipped" } + if ($pendingJobs -gt 0) { $parts += "$pendingJobs pending" } + $jobSummary = $parts -join ", " + $allSucceeded = ($failedJobs.Count -eq 0 -and $pendingJobs -eq 0 -and $canceledJobCount -eq 0 -and ($succeededJobs + $warningJobs + $skippedJobs) -eq $allJobs.Count) + $summaryColor = if ($allSucceeded) { "Green" } elseif ($failedJobs.Count -gt 0) { "Red" } else { "Cyan" } + Write-Host "Jobs: $($allJobs.Count) total ($jobSummary)" -ForegroundColor $summaryColor + } + else { + Write-Host "Failed jobs: $($failedJobs.Count)" -ForegroundColor Red + } if ($localTestFailures.Count -gt 0) { Write-Host "Local test failures: $($localTestFailures.Count)" -ForegroundColor Red }