Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/skills/azdo-helix-failures/SKILL.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
54 changes: 52 additions & 2 deletions .github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @{}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down