From a9ea742893e90c3ccd110c292491cf86e7bffb10 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 6 Feb 2026 23:18:55 -0600 Subject: [PATCH 1/2] Fix Helix file URIs for subdirectory artifacts (xharness-output) Workaround for dotnet/dnceng#6072: Helix API returns incorrect file URIs for files uploaded from subdirectories (e.g., xharness-output/testResults.xml). Rebuild URIs from the FileName field which correctly includes the path. --- .../references/helix-artifacts.md | 4 ++++ .../scripts/Get-HelixFailures.ps1 | 14 ++++++++++++++ 2 files changed, 18 insertions(+) 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..a65ec6f72e192d 100644 --- a/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 +++ b/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 @@ -1353,6 +1353,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 { From 69319e2fa2e1f339b661864485c67acb8ca99cf6 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 7 Feb 2026 02:28:49 -0600 Subject: [PATCH 2/2] Broaden skill to CI status analysis, add job summary - Update SKILL.md description to cover CI status checks, not just failures - Add trigger phrases: 'is CI passing', 'build status', 'ready to merge' - Add job summary in build output (e.g., '143 total: 142 passed, 1 failed') - Improve no-build-found message to explain CI hasn't been triggered --- .github/skills/azdo-helix-failures/SKILL.md | 10 +++-- .../scripts/Get-HelixFailures.ps1 | 42 +++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) 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/scripts/Get-HelixFailures.ps1 b/.github/skills/azdo-helix-failures/scripts/Get-HelixFailures.ps1 index a65ec6f72e192d..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 @@ -1362,7 +1367,7 @@ function Get-HelixWorkItemDetails { 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" + $file.Uri = "https://helix.dot.net/api/jobs/$JobId/workitems/$encodedWorkItem/files/$encodedFileName?api-version=2019-06-17" } } } @@ -2026,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 }