From e7003350ef899a857e04a57e086e6b26dc5df78a Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 12:34:54 +0100 Subject: [PATCH 1/9] Improve azdo-build-investigator skill triggering for CI queries The skill was not being invoked when users asked "check CI status" or "why is CI failing" because the description focused on AZDO URLs and .binlog analysis. The LLM would check gh pr checks first (which shows all green) and never reach the skill. Changes: - SKILL.md: Rewrite description to lead with common trigger phrases (CI status, CI failures, PR blocked) and emphasize this is the PRIMARY CI tool since the real CI runs on Azure DevOps - copilot-instructions.md: Add "CI / Build Investigation" section explaining the Azure DevOps CI architecture and that the skill must always be invoked first Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 8 ++++++++ .github/skills/azdo-build-investigator/SKILL.md | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 906ab863b91..3a4e3363b89 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -190,6 +190,14 @@ This pattern ensures proper encoding, timestamps, and file attributes are handle - **Error code lifecycle:** When removing functionality that used an `XA####` code, either repurpose the code or remove it from `Resources.resx` and `Resources.Designer.cs`. Don't leave orphaned codes. - **Logging in `AsyncTask`:** Use the thread-safe helpers (`LogCodedError()`, `LogMessage()`, `LogCodedWarning()`, `LogDebugMessage()`) instead of `Log.*`. The `Log` property is marked `[Obsolete]` on `AsyncTask` because calling `Log.LogMessage` directly from a background thread can hang Visual Studio. +## CI / Build Investigation + +**dotnet/android's primary CI runs on Azure DevOps (internal), not GitHub Actions.** When a user asks about CI status, CI failures, why a PR is blocked, or build errors: + +1. **ALWAYS invoke the `azdo-build-investigator` skill first** — do NOT rely on `gh pr checks` alone. GitHub checks may all show ✅ while the internal Azure DevOps build is failing. +2. The skill can resolve a GitHub PR number to its Azure DevOps build and fetch logs, timeline, and .binlog artifacts. +3. Only after the skill confirms no Azure DevOps failures should you report CI as passing. + ## Troubleshooting - **Build:** Clean `bin/`+`obj/`, check Android SDK/NDK, `make clean` - **MSBuild:** Test in isolation, validate inputs diff --git a/.github/skills/azdo-build-investigator/SKILL.md b/.github/skills/azdo-build-investigator/SKILL.md index 3dc9ca48fd6..2f3b0af2536 100644 --- a/.github/skills/azdo-build-investigator/SKILL.md +++ b/.github/skills/azdo-build-investigator/SKILL.md @@ -1,10 +1,17 @@ --- name: azdo-build-investigator -description: Investigate Azure DevOps (AZDO) pipeline build failures by fetching logs, downloading artifacts, and analyzing .binlog files to find the root cause of errors. Use this when users share an AZDO build URL, a GitHub PR URL, or ask about a failing pipeline, build errors, or CI failures. +description: > + ALWAYS use this skill when the user asks about CI status, CI failures, build status, why CI is failing, + why a PR is blocked, or anything related to CI/build investigation on a PR. This is the PRIMARY CI + investigation tool because dotnet/android's main CI runs on Azure DevOps (not GitHub Actions). GitHub + check status alone is insufficient — all GitHub checks may show green while the internal Azure DevOps + build is failing. Also use when the user shares an AZDO build URL or asks about a failing pipeline. --- # AZDO Build Investigator +**This is the primary CI investigation tool for dotnet/android.** The repo's real CI runs on Azure DevOps internally. GitHub Actions checks (visible via `gh pr checks`) only cover a subset of validation. When a user asks about CI status or failures, ALWAYS use this skill — do NOT rely solely on GitHub check status. + Given a build URL or GitHub PR URL, fetch run details, find failed jobs/tasks, download logs and .binlog artifacts, and produce a summarized root-cause error trail. ## Prerequisites From f41bc4981aeb68dd10f8a743aa815eae4d94968b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 12:53:59 +0100 Subject: [PATCH 2/9] Add ci-status skill with auto-detect, two-phase workflow, and reference files New skill that serves as the primary entry point for CI status queries: - Auto-detects current PR from git branch (no URL needed) - Phase 1: Quick summary of GitHub checks + AZDO build status - Phase 2: Deep investigation (logs, binlogs) only on request - Reference files loaded conditionally (error-patterns, binlog-analysis) - Both bash and PowerShell examples throughout - Clear boundaries, error handling, and output format spec - copilot-instructions.md updated to point to ci-status as primary Inspired by DataDog/dd-trace-dotnet analyze-azdo-build skill patterns and Anthropic skill creation best practices. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 7 +- .github/skills/ci-status/SKILL.md | 156 ++++++++++++++++++ .../ci-status/references/binlog-analysis.md | 74 +++++++++ .../ci-status/references/error-patterns.md | 47 ++++++ 4 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 .github/skills/ci-status/SKILL.md create mode 100644 .github/skills/ci-status/references/binlog-analysis.md create mode 100644 .github/skills/ci-status/references/error-patterns.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3a4e3363b89..9aca8fa06ae 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -194,9 +194,10 @@ This pattern ensures proper encoding, timestamps, and file attributes are handle **dotnet/android's primary CI runs on Azure DevOps (internal), not GitHub Actions.** When a user asks about CI status, CI failures, why a PR is blocked, or build errors: -1. **ALWAYS invoke the `azdo-build-investigator` skill first** — do NOT rely on `gh pr checks` alone. GitHub checks may all show ✅ while the internal Azure DevOps build is failing. -2. The skill can resolve a GitHub PR number to its Azure DevOps build and fetch logs, timeline, and .binlog artifacts. -3. Only after the skill confirms no Azure DevOps failures should you report CI as passing. +1. **ALWAYS invoke the `ci-status` skill first** — do NOT rely on `gh pr checks` alone. GitHub checks may all show ✅ while the internal Azure DevOps build is failing. +2. The skill auto-detects the current PR from the git branch when no PR number is given. +3. For deep .binlog analysis, use the `azdo-build-investigator` skill. +4. Only after the skill confirms no Azure DevOps failures should you report CI as passing. ## Troubleshooting - **Build:** Clean `bin/`+`obj/`, check Android SDK/NDK, `make clean` diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md new file mode 100644 index 00000000000..f07a183401e --- /dev/null +++ b/.github/skills/ci-status/SKILL.md @@ -0,0 +1,156 @@ +--- +name: ci-status +description: > + Check CI build status and investigate failures for dotnet/android PRs. ALWAYS use this skill when + the user asks "check CI", "CI status", "why is CI failing", "is CI green", "why is my PR blocked", + or anything about build status on a PR. Auto-detects the current PR from the git branch when no + PR number is given. Covers both GitHub checks and internal Azure DevOps builds. + DO NOT USE FOR: GitHub Actions workflow authoring, non-dotnet/android repos. +--- + +# CI Status + +Check CI status and investigate build failures for dotnet/android PRs. + +**Key fact:** dotnet/android's primary CI runs on Azure DevOps (internal). GitHub checks alone are insufficient — they may all show ✅ while the internal build is failing. + +## Prerequisites + +| Tool | Check | Setup | +|------|-------|-------| +| `gh` | `gh --version` | https://cli.github.com/ | +| `az` + devops ext | `az version` | `az extension add --name azure-devops` then `az login` | + +If `az` is not authenticated, stop and tell the user to run `az login`. + +## Workflow + +### Phase 1: Quick Status (always do this first) + +#### Step 1 — Resolve the PR + +**No PR specified** — detect from current branch: + +```bash +gh pr view --json number,title,url,headRefName --jq '{number,title,url,headRefName}' +``` + +```powershell +gh pr view --json number,title,url,headRefName | ConvertFrom-Json +``` + +If no PR exists for the current branch, tell the user and stop. + +**PR number given** — use it directly. + +#### Step 2 — Get GitHub check status + +```bash +gh pr checks $PR --repo dotnet/android --json "name,state,link,bucket" 2>&1 \ + | jq '[.[] | {name, state, bucket, link}]' +``` + +```powershell +gh pr checks $PR --repo dotnet/android --json "name,state,link,bucket" | ConvertFrom-Json +``` + +Note which checks passed/failed/pending. The `link` field contains the AZDO build URL for internal checks. + +#### Step 3 — Get Azure DevOps build status + +Extract the AZDO build URL from the check `link` fields. Parse `{orgUrl}`, `{project}`, and `{buildId}` from patterns: +- `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` +- `https://{org}.visualstudio.com/{project}/_build/results?buildId={id}` + +Then fetch the build timeline: + +```bash +az devops invoke --area build --resource timeline \ + --route-parameters project=$PROJECT buildId=$BUILD_ID \ + --org $ORG_URL --project $PROJECT \ + --query "records[?result=='failed'] | [].{name:name, type:type, result:result, issues:issues, errorCount:errorCount, log:log}" \ + --output json 2>&1 +``` + +```powershell +az devops invoke --area build --resource timeline ` + --route-parameters project=$PROJECT buildId=$BUILD_ID ` + --org $ORG_URL --project $PROJECT ` + --query "records[?result=='failed'] | [].{name:name, type:type, result:result, issues:issues, errorCount:errorCount, log:log}" ` + --output json +``` + +Check `issues` arrays first — they often contain the root cause directly. + +#### Step 4 — Present summary + +Use this format: + +``` +# CI Status for PR #NNNN — "PR Title" + +## GitHub Checks +| Check | Status | +|-------|--------| +| check-name | ✅ / ❌ / 🟡 | + +## Azure DevOps Build [#BuildId](link) +**Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress + +### Failures (if any) +❌ Stage > Job > Task + Error: + +## What next? +1. View full logs for a failure +2. Download and analyze .binlog artifacts +3. Retry failed stages +``` + +**If no failures found anywhere**, report CI as green and stop. + +### Phase 2: Deep Investigation (only if user requests) + +Only proceed here if the user asks to investigate a specific failure, view logs, or analyze binlogs. + +#### Fetch logs + +Get the `log.id` from failed timeline records, then: + +```bash +az devops invoke --area build --resource logs \ + --route-parameters project=$PROJECT buildId=$BUILD_ID logId=$LOG_ID \ + --org $ORG_URL --project $PROJECT \ + --out-file "/tmp/azdo-log-$LOG_ID.log" 2>&1 +tail -40 "/tmp/azdo-log-$LOG_ID.log" +``` + +```powershell +$logFile = Join-Path $env:TEMP "azdo-log-$LOG_ID.log" +az devops invoke --area build --resource logs ` + --route-parameters project=$PROJECT buildId=$BUILD_ID logId=$LOG_ID ` + --org $ORG_URL --project $PROJECT ` + --out-file $logFile +Get-Content $logFile -Tail 40 +``` + +#### Analyze .binlog artifacts + +See [references/binlog-analysis.md](references/binlog-analysis.md) for binlog download and analysis commands. + +#### Categorize failures + +See [references/error-patterns.md](references/error-patterns.md) for dotnet/android-specific error patterns and categorization. + +## Error Handling + +- **Build in progress:** Report current status and offer to watch with `gh pr checks --watch` +- **No AZDO build found:** The PR may not have triggered internal CI yet. Report GitHub checks only. +- **Auth expired:** Tell user to run `az login` and retry. +- **Build not found:** Verify the PR number/build ID is correct. + +## Tips + +- Focus on the **first** error chronologically — later errors often cascade +- `.binlog` has richer detail than text logs when logs show only "Build FAILED" +- `issues` in timeline records often contain the root cause without needing to download logs diff --git a/.github/skills/ci-status/references/binlog-analysis.md b/.github/skills/ci-status/references/binlog-analysis.md new file mode 100644 index 00000000000..320b7fb9853 --- /dev/null +++ b/.github/skills/ci-status/references/binlog-analysis.md @@ -0,0 +1,74 @@ +# Binlog Analysis Reference + +Load this file only when the user asks to analyze .binlog artifacts from a build. + +## Prerequisites + +| Tool | Check | Install | +|------|-------|---------| +| `binlogtool` | `dotnet tool list -g \| grep binlogtool` | `dotnet tool install -g binlogtool` | + +## Download .binlog artifacts + +### List artifacts + +```bash +az pipelines runs artifact list --run-id $BUILD_ID --org $ORG_URL --project $PROJECT --output json 2>&1 +``` + +```powershell +az pipelines runs artifact list --run-id $BUILD_ID --org $ORG_URL --project $PROJECT --output json +``` + +Look for artifact names containing `binlog`, `msbuild`, or `build-log`. + +### Download + +```bash +TEMP_DIR="/tmp/azdo-binlog-$BUILD_ID" +mkdir -p "$TEMP_DIR" +az pipelines runs artifact download --artifact-name "$ARTIFACT_NAME" --path "$TEMP_DIR" \ + --run-id $BUILD_ID --org $ORG_URL --project $PROJECT +``` + +```powershell +$tempDir = Join-Path $env:TEMP "azdo-binlog-$BUILD_ID" +New-Item -ItemType Directory -Path $tempDir -Force | Out-Null +az pipelines runs artifact download --artifact-name "$ARTIFACT_NAME" --path $tempDir ` + --run-id $BUILD_ID --org $ORG_URL --project $PROJECT +``` + +## Analysis commands + +```bash +# Broad error search +binlogtool search "$TEMP_DIR"/*.binlog "error" + +# .NET Android errors +binlogtool search "$TEMP_DIR"/*.binlog "XA" + +# C# compiler errors +binlogtool search "$TEMP_DIR"/*.binlog "error CS" + +# NuGet errors +binlogtool search "$TEMP_DIR"/*.binlog "error NU" + +# Full text log reconstruction +binlogtool reconstruct "$TEMP_DIR/file.binlog" "$TEMP_DIR/reconstructed" + +# MSBuild properties +binlogtool listproperties "$TEMP_DIR/file.binlog" + +# Double-write detection +binlogtool doublewrites "$TEMP_DIR/file.binlog" "$TEMP_DIR/dw" +``` + +## Cleanup + +```bash +rm -rf "/tmp/azdo-binlog-$BUILD_ID" +``` + +```powershell +Remove-Item -Recurse -Force (Join-Path $env:TEMP "azdo-binlog-$BUILD_ID") +``` diff --git a/.github/skills/ci-status/references/error-patterns.md b/.github/skills/ci-status/references/error-patterns.md new file mode 100644 index 00000000000..f705a080548 --- /dev/null +++ b/.github/skills/ci-status/references/error-patterns.md @@ -0,0 +1,47 @@ +# Error Patterns (dotnet/android) + +Load this file only during failure categorization or when investigating a specific error. + +## Categories + +### 🔴 Real Failures — Investigate + +These indicate genuine code problems that need fixing. + +| Pattern | Example | +|---------|---------| +| MSBuild errors | `XA####`, `APT####` | +| C# compiler | `error CS####` | +| NuGet resolution | `NU1100`–`NU1699` | +| Test assertions | `Failed :`, `Assert.`, `Expected:` | +| Segfaults / crashes | `SIGSEGV`, `SIGABRT`, `Fatal error` | + +### 🟡 Flaky Failures — Retry + +These are known intermittent issues. + +| Pattern | Example | +|---------|---------| +| Device connectivity | `device not found`, `adb: device offline` | +| Emulator timeouts | `System.TimeoutException`, `emulator did not boot` | +| Single-platform failure | Test fails on one OS but passes on others | + +### 🔵 Infrastructure — Retry + +These are CI environment issues, not code problems. + +| Pattern | Example | +|---------|---------| +| Disk space | `No space left on device`, `NOSPC` | +| Network | `Unable to load the service index`, `Connection refused` | +| NuGet feed | `NU1301` (feed connectivity) | +| Agent issues | `The agent did not connect`, `##[error] The job was canceled` | +| Timeout (job-level) | Job canceled after 55+ minutes | + +## Decision Tree + +1. Does the error contain `XA`, `CS`, `NU1[1-6]`, or `Assert`? → 🔴 Real +2. Does the error mention `device`, `emulator`, `adb`, or `TimeoutException`? → 🟡 Flaky +3. Does the error mention `disk`, `network`, `feed`, `agent`, or `##[error] canceled`? → 🔵 Infra +4. Does the same test pass on other platforms in the same build? → 🟡 Flaky +5. Otherwise → 🔴 Real (default to investigating) From f3bbaa66fffff7aefed7ecc5003f0be79cb6fe47 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 13:25:28 +0100 Subject: [PATCH 3/9] Revert azdo-build-investigator SKILL.md changes The CI investigation improvements are now in the dedicated ci-status skill and copilot-instructions.md instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/azdo-build-investigator/SKILL.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/skills/azdo-build-investigator/SKILL.md b/.github/skills/azdo-build-investigator/SKILL.md index 2f3b0af2536..3dc9ca48fd6 100644 --- a/.github/skills/azdo-build-investigator/SKILL.md +++ b/.github/skills/azdo-build-investigator/SKILL.md @@ -1,17 +1,10 @@ --- name: azdo-build-investigator -description: > - ALWAYS use this skill when the user asks about CI status, CI failures, build status, why CI is failing, - why a PR is blocked, or anything related to CI/build investigation on a PR. This is the PRIMARY CI - investigation tool because dotnet/android's main CI runs on Azure DevOps (not GitHub Actions). GitHub - check status alone is insufficient — all GitHub checks may show green while the internal Azure DevOps - build is failing. Also use when the user shares an AZDO build URL or asks about a failing pipeline. +description: Investigate Azure DevOps (AZDO) pipeline build failures by fetching logs, downloading artifacts, and analyzing .binlog files to find the root cause of errors. Use this when users share an AZDO build URL, a GitHub PR URL, or ask about a failing pipeline, build errors, or CI failures. --- # AZDO Build Investigator -**This is the primary CI investigation tool for dotnet/android.** The repo's real CI runs on Azure DevOps internally. GitHub Actions checks (visible via `gh pr checks`) only cover a subset of validation. When a user asks about CI status or failures, ALWAYS use this skill — do NOT rely solely on GitHub check status. - Given a build URL or GitHub PR URL, fetch run details, find failed jobs/tasks, download logs and .binlog artifacts, and produce a summarized root-cause error trail. ## Prerequisites From 9c5252d1f9f0345301007ac5c54da5f418b45121 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 14:22:12 +0100 Subject: [PATCH 4/9] ci-status skill: surface failed tests while build is in progress Add Step 3b to query AZDO test runs API for already-published test failures even when the overall build is still running. Update the output format to show failed test names and error messages prominently so they can be investigated immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/ci-status/SKILL.md | 86 +++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md index f07a183401e..8a97d77807a 100644 --- a/.github/skills/ci-status/SKILL.md +++ b/.github/skills/ci-status/SKILL.md @@ -62,26 +62,71 @@ Extract the AZDO build URL from the check `link` fields. Parse `{orgUrl}`, `{pro - `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` - `https://{org}.visualstudio.com/{project}/_build/results?buildId={id}` -Then fetch the build timeline: +First, get the overall build status: + +```bash +az devops invoke --area build --resource builds \ + --route-parameters project=$PROJECT buildId=$BUILD_ID \ + --org $ORG_URL \ + --query "{status:status, result:result}" --output json 2>&1 +``` + +Then fetch the build timeline for **all completed jobs** and **any failures so far** — even when the build is still in progress: ```bash az devops invoke --area build --resource timeline \ --route-parameters project=$PROJECT buildId=$BUILD_ID \ - --org $ORG_URL --project $PROJECT \ - --query "records[?result=='failed'] | [].{name:name, type:type, result:result, issues:issues, errorCount:errorCount, log:log}" \ + --org $ORG_URL \ + --query "records[?type=='Job'] | [].{name:name, state:state, result:result}" \ --output json 2>&1 ``` -```powershell -az devops invoke --area build --resource timeline ` - --route-parameters project=$PROJECT buildId=$BUILD_ID ` - --org $ORG_URL --project $PROJECT ` - --query "records[?result=='failed'] | [].{name:name, type:type, result:result, issues:issues, errorCount:errorCount, log:log}" ` - --output json +```bash +az devops invoke --area build --resource timeline \ + --route-parameters project=$PROJECT buildId=$BUILD_ID \ + --org $ORG_URL \ + --query "records[?result=='failed'] | [].{name:name, type:type, result:result, issues:issues, errorCount:errorCount, log:log}" \ + --output json 2>&1 ``` Check `issues` arrays first — they often contain the root cause directly. +#### Step 3b — Check for failed tests (always do this, especially when the build is still running) + +**This step is critical when the build is in progress.** Test results are published as jobs complete, so failures may already be visible before the build finishes. Surfacing these early lets the user start fixing them immediately. + +Query test runs for this build: + +```bash +az devops invoke --area test --resource runs \ + --route-parameters project=$PROJECT \ + --org $ORG_URL \ + --query-parameters "buildUri=vstfs:///Build/Build/$BUILD_ID" \ + --query "value[?runStatistics[?outcome=='Failed']] | [].{id:id, name:name, totalTests:totalTests, state:state, stats:runStatistics}" \ + --output json 2>&1 +``` + +For each test run that has failures, fetch the failed test results: + +```bash +az devops invoke --area test --resource results \ + --route-parameters project=$PROJECT runId=$RUN_ID \ + --org $ORG_URL \ + --query-parameters "outcomes=Failed&\$top=20" \ + --query "value[].{testName:testCaseTitle, outcome:outcome, errorMessage:errorMessage, durationMs:durationInMs}" \ + --output json 2>&1 +``` + +If the `errorMessage` is truncated or absent, you can fetch a single test result's full details: + +```bash +az devops invoke --area test --resource results \ + --route-parameters project=$PROJECT runId=$RUN_ID testId=$TEST_ID \ + --org $ORG_URL \ + --query "{testName:testCaseTitle, errorMessage:errorMessage, stackTrace:stackTrace}" \ + --output json 2>&1 +``` + #### Step 4 — Present summary Use this format: @@ -97,16 +142,34 @@ Use this format: ## Azure DevOps Build [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress +### Job Status (when build is in progress, show all jobs) +| Job | Status | +|-----|--------| +| job-name | ✅ Succeeded / ❌ Failed / 🟡 In Progress | + ### Failures (if any) ❌ Stage > Job > Task Error: +### Failed Tests (if any — even while build is still running) +| Test Run | Failed | Total | +|----------|--------|-------| +| run-name | N | M | + +**Failed test names:** +- `Namespace.TestClass.TestMethod` — brief error message +- ... + ## What next? -1. View full logs for a failure +1. View full logs / stack traces for a test failure 2. Download and analyze .binlog artifacts 3. Retry failed stages ``` +**If the build is still running but tests have already failed**, highlight these prominently so the user can start fixing them immediately. Use a note like: + +> ⚠️ Build still in progress, but **N tests have already failed** — you can start investigating these now. + **If no failures found anywhere**, report CI as green and stop. ### Phase 2: Deep Investigation (only if user requests) @@ -144,10 +207,11 @@ See [references/error-patterns.md](references/error-patterns.md) for dotnet/andr ## Error Handling -- **Build in progress:** Report current status and offer to watch with `gh pr checks --watch` +- **Build in progress:** Still query for failed timeline records AND test runs. Report any early failures alongside the in-progress status. Only offer `gh pr checks --watch` if there are no failures yet. - **No AZDO build found:** The PR may not have triggered internal CI yet. Report GitHub checks only. - **Auth expired:** Tell user to run `az login` and retry. - **Build not found:** Verify the PR number/build ID is correct. +- **No test runs yet:** The build may not have reached the test phase. Report what's available and note that tests haven't started. ## Tips From 3f7a77878acc1711373cdcca4c30a50a471200b7 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 14:28:33 +0100 Subject: [PATCH 5/9] ci-status skill: add build progress tracking and ETA Show job progress counters (completed/running/waiting), elapsed time, and estimated completion based on median duration of last 5 successful runs of the same pipeline definition. Also handles overdue builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/ci-status/SKILL.md | 52 ++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md index 8a97d77807a..25cf6c7b8e1 100644 --- a/.github/skills/ci-status/SKILL.md +++ b/.github/skills/ci-status/SKILL.md @@ -62,16 +62,19 @@ Extract the AZDO build URL from the check `link` fields. Parse `{orgUrl}`, `{pro - `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` - `https://{org}.visualstudio.com/{project}/_build/results?buildId={id}` -First, get the overall build status: +First, get the overall build status including start time and definition ID: ```bash az devops invoke --area build --resource builds \ --route-parameters project=$PROJECT buildId=$BUILD_ID \ --org $ORG_URL \ - --query "{status:status, result:result}" --output json 2>&1 + --query "{status:status, result:result, startTime:startTime, definitionId:definition.id, definitionName:definition.name}" \ + --output json 2>&1 ``` -Then fetch the build timeline for **all completed jobs** and **any failures so far** — even when the build is still in progress: +**Compute elapsed time:** Subtract `startTime` from the current time. Present as e.g. "Running for 42 min". + +Then fetch the build timeline for **all jobs** (to get progress counts) and **any failures so far** — even when the build is still in progress: ```bash az devops invoke --area build --resource timeline \ @@ -81,6 +84,14 @@ az devops invoke --area build --resource timeline \ --output json 2>&1 ``` +**Compute job progress counters** from the timeline response: +- Count jobs where `state == 'completed'` → **finished** +- Count jobs where `state == 'inProgress'` → **running** +- Count jobs where `state == 'pending'` → **waiting** +- Total = finished + running + waiting + +Then fetch failures: + ```bash az devops invoke --area build --resource timeline \ --route-parameters project=$PROJECT buildId=$BUILD_ID \ @@ -91,6 +102,27 @@ az devops invoke --area build --resource timeline \ Check `issues` arrays first — they often contain the root cause directly. +#### Step 3a — Estimate completion time (when build is in progress) + +Use the `definitionId` from the build to query recent successful builds of the **same pipeline** and compute average duration: + +```bash +az devops invoke --area build --resource builds \ + --route-parameters project=$PROJECT \ + --org $ORG_URL \ + --query-parameters "definitions=$DEF_ID&statusFilter=completed&resultFilter=succeeded&\$top=5" \ + --query "value[].{startTime:startTime, finishTime:finishTime}" \ + --output json 2>&1 +``` + +**Compute ETA:** +1. For each recent build, calculate `duration = finishTime - startTime` +2. Compute the **median** duration (more robust than average against outliers) +3. `ETA = startTime + medianDuration` +4. Present as: "ETA: ~14:30 UTC (based on median of last 5 runs: ~2h 15min)" + +If `startTime` is null (build hasn't started yet), skip the ETA and say "Build queued, not started yet". + #### Step 3b — Check for failed tests (always do this, especially when the build is still running) **This step is critical when the build is in progress.** Test results are published as jobs complete, so failures may already be visible before the build finishes. Surfacing these early lets the user start fixing them immediately. @@ -142,10 +174,14 @@ Use this format: ## Azure DevOps Build [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress -### Job Status (when build is in progress, show all jobs) +### Progress (show when build is in progress) +⏱️ Running for **42 min** · ETA: ~14:30 UTC (median of last 5 runs: ~2h 15min) +📊 Jobs: **18/56 completed** · 6 running · 32 waiting + +### Job Status | Job | Status | |-----|--------| -| job-name | ✅ Succeeded / ❌ Failed / 🟡 In Progress | +| job-name | ✅ Succeeded / ❌ Failed / 🟡 In Progress / ⏳ Waiting | ### Failures (if any) ❌ Stage > Job > Task @@ -166,6 +202,12 @@ Use this format: 3. Retry failed stages ``` +**Progress section guidelines:** +- Always show elapsed time when `startTime` is available +- Show ETA when the build is in progress and historical data is available. If the build has been running longer than the median, say "overdue by ~X min" +- Show job counters as "N/Total completed · M running · P waiting" +- If the build hasn't started yet, show "⏳ Build queued, not started yet" + **If the build is still running but tests have already failed**, highlight these prominently so the user can start fixing them immediately. Use a note like: > ⚠️ Build still in progress, but **N tests have already failed** — you can start investigating these now. From e8096dc0981104f22b4eae85e735abee9a29dbbf Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 14:56:12 +0100 Subject: [PATCH 6/9] ci-status skill: per-build progress and ETA Show separate progress counters and ETA for each AZDO build (public dotnet-android and internal Xamarin.Android-PR). Each pipeline has its own definition ID and typical duration, so ETAs are computed independently from the last 5 successful runs of each. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/ci-status/SKILL.md | 44 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md index 25cf6c7b8e1..f72a4c5b9a9 100644 --- a/.github/skills/ci-status/SKILL.md +++ b/.github/skills/ci-status/SKILL.md @@ -56,23 +56,29 @@ gh pr checks $PR --repo dotnet/android --json "name,state,link,bucket" | Convert Note which checks passed/failed/pending. The `link` field contains the AZDO build URL for internal checks. -#### Step 3 — Get Azure DevOps build status +#### Step 3 — Get Azure DevOps build status (repeat for EACH build) -Extract the AZDO build URL from the check `link` fields. Parse `{orgUrl}`, `{project}`, and `{buildId}` from patterns: +There are typically **two separate AZDO builds** for a dotnet/android PR: +- **Public** (`dotnet-android`) on `dev.azure.com/dnceng-public` — compiles on Linux, macOS, Windows +- **Internal** (`Xamarin.Android-PR`) on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance + +Extract AZDO build URLs from the check `link` fields. Parse `{orgUrl}`, `{project}`, and `{buildId}` from patterns: - `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` - `https://{org}.visualstudio.com/{project}/_build/results?buildId={id}` -First, get the overall build status including start time and definition ID: +**Run Steps 3, 3a, and 3b for each AZDO build independently.** The builds have different pipelines, different job counts, and different typical durations — each gets its own progress and ETA. + +For each build, first get the overall status including start time and definition ID: ```bash az devops invoke --area build --resource builds \ --route-parameters project=$PROJECT buildId=$BUILD_ID \ --org $ORG_URL \ - --query "{status:status, result:result, startTime:startTime, definitionId:definition.id, definitionName:definition.name}" \ + --query "{status:status, result:result, startTime:startTime, finishTime:finishTime, definitionId:definition.id, definitionName:definition.name}" \ --output json 2>&1 ``` -**Compute elapsed time:** Subtract `startTime` from the current time. Present as e.g. "Running for 42 min". +**Compute elapsed time:** Subtract `startTime` from the current time (or from `finishTime` if the build is complete). Present as e.g. "Ran for 42 min" or "Running for 42 min". Then fetch the build timeline for **all jobs** (to get progress counts) and **any failures so far** — even when the build is still in progress: @@ -102,9 +108,9 @@ az devops invoke --area build --resource timeline \ Check `issues` arrays first — they often contain the root cause directly. -#### Step 3a — Estimate completion time (when build is in progress) +#### Step 3a — Estimate completion time per build (when build is in progress) -Use the `definitionId` from the build to query recent successful builds of the **same pipeline** and compute average duration: +Use the `definitionId` from the build to query recent successful builds of the **same pipeline definition** and compute the median duration. **Do this separately for each build** — the public and internal pipelines have very different durations. ```bash az devops invoke --area build --resource builds \ @@ -119,9 +125,10 @@ az devops invoke --area build --resource builds \ 1. For each recent build, calculate `duration = finishTime - startTime` 2. Compute the **median** duration (more robust than average against outliers) 3. `ETA = startTime + medianDuration` -4. Present as: "ETA: ~14:30 UTC (based on median of last 5 runs: ~2h 15min)" +4. Present as: "ETA: ~14:30 UTC (median of last 5 runs: ~2h 15min)" If `startTime` is null (build hasn't started yet), skip the ETA and say "Build queued, not started yet". +If the build already completed, skip the ETA and show the actual duration instead. #### Step 3b — Check for failed tests (always do this, especially when the build is still running) @@ -161,7 +168,7 @@ az devops invoke --area test --resource results \ #### Step 4 — Present summary -Use this format: +Use this format — **one section per AZDO build**, each with its own progress and ETA: ``` # CI Status for PR #NNNN — "PR Title" @@ -171,17 +178,21 @@ Use this format: |-------|--------| | check-name | ✅ / ❌ / 🟡 | -## Azure DevOps Build [#BuildId](link) +## Public Build: dotnet-android [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress +⏱️ Running for **12 min** · ETA: ~15:15 UTC (typical: ~1h 45min) +📊 Jobs: **0/3 completed** · 1 running · 2 waiting -### Progress (show when build is in progress) -⏱️ Running for **42 min** · ETA: ~14:30 UTC (median of last 5 runs: ~2h 15min) -📊 Jobs: **18/56 completed** · 6 running · 32 waiting - -### Job Status | Job | Status | |-----|--------| -| job-name | ✅ Succeeded / ❌ Failed / 🟡 In Progress / ⏳ Waiting | +| macOS > Build | 🟡 In Progress | +| Linux > Build | ⏳ Waiting | +| Windows > Build & Smoke Test | ⏳ Waiting | + +## Internal Build: Xamarin.Android-PR [#BuildId](link) +**Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress +⏱️ Running for **42 min** · ETA: ~15:45 UTC (typical: ~2h 30min) +📊 Jobs: **18/56 completed** · 6 running · 32 waiting ### Failures (if any) ❌ Stage > Job > Task @@ -207,6 +218,7 @@ Use this format: - Show ETA when the build is in progress and historical data is available. If the build has been running longer than the median, say "overdue by ~X min" - Show job counters as "N/Total completed · M running · P waiting" - If the build hasn't started yet, show "⏳ Build queued, not started yet" +- If only one AZDO build exists (e.g., `.github/`-only PRs don't trigger internal), just show that one **If the build is still running but tests have already failed**, highlight these prominently so the user can start fixing them immediately. Use a note like: From 112ff42029bea4526f326f897b1d718362fac33b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 15:02:33 +0100 Subject: [PATCH 7/9] ci-status skill: use pipeline names instead of Public/Internal labels Label builds by their definitionName (e.g. 'dotnet-android', 'Xamarin.Android-PR') instead of misleading 'Public'/'Internal'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/ci-status/SKILL.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md index f72a4c5b9a9..598897c813a 100644 --- a/.github/skills/ci-status/SKILL.md +++ b/.github/skills/ci-status/SKILL.md @@ -59,8 +59,10 @@ Note which checks passed/failed/pending. The `link` field contains the AZDO buil #### Step 3 — Get Azure DevOps build status (repeat for EACH build) There are typically **two separate AZDO builds** for a dotnet/android PR: -- **Public** (`dotnet-android`) on `dev.azure.com/dnceng-public` — compiles on Linux, macOS, Windows -- **Internal** (`Xamarin.Android-PR`) on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance +- **`dotnet-android`** on `dev.azure.com/dnceng-public` — compiles on Linux, macOS, Windows +- **`Xamarin.Android-PR`** on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance + +Use the **pipeline definition name** (from the `definitionName` field) as the label in output — do NOT label them "Public" or "Internal". Extract AZDO build URLs from the check `link` fields. Parse `{orgUrl}`, `{project}`, and `{buildId}` from patterns: - `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` @@ -178,7 +180,7 @@ Use this format — **one section per AZDO build**, each with its own progress a |-------|--------| | check-name | ✅ / ❌ / 🟡 | -## Public Build: dotnet-android [#BuildId](link) +## dotnet-android [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress ⏱️ Running for **12 min** · ETA: ~15:15 UTC (typical: ~1h 45min) 📊 Jobs: **0/3 completed** · 1 running · 2 waiting @@ -189,7 +191,7 @@ Use this format — **one section per AZDO build**, each with its own progress a | Linux > Build | ⏳ Waiting | | Windows > Build & Smoke Test | ⏳ Waiting | -## Internal Build: Xamarin.Android-PR [#BuildId](link) +## Xamarin.Android-PR [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress ⏱️ Running for **42 min** · ETA: ~15:45 UTC (typical: ~2h 30min) 📊 Jobs: **18/56 completed** · 6 running · 32 waiting From 5852c11fb12e6dc89703cba3c2f082c7567f4554 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 15:09:05 +0100 Subject: [PATCH 8/9] ci-status skill: document pipeline triggers and 'Expected' state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two AZDO pipelines (dotnet-android and Xamarin.Android-PR) run independently — neither waits for the other. Document that Xamarin.Android-PR's PR trigger is configured in AZDO UI (not YAML) and may take a few minutes to start. Handle the GitHub 'Expected' check state explicitly instead of vaguely saying 'not found'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/ci-status/SKILL.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md index 598897c813a..61383ba363a 100644 --- a/.github/skills/ci-status/SKILL.md +++ b/.github/skills/ci-status/SKILL.md @@ -58,12 +58,14 @@ Note which checks passed/failed/pending. The `link` field contains the AZDO buil #### Step 3 — Get Azure DevOps build status (repeat for EACH build) -There are typically **two separate AZDO builds** for a dotnet/android PR: -- **`dotnet-android`** on `dev.azure.com/dnceng-public` — compiles on Linux, macOS, Windows -- **`Xamarin.Android-PR`** on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance +There are typically **two separate AZDO builds** for a dotnet/android PR. They run **independently** — neither waits for the other: +- **`dotnet-android`** on `dev.azure.com/dnceng-public` — compiles on Linux, macOS, Windows. Defined in `azure-pipelines-public.yaml` with an explicit `pr:` trigger. For non-fork PRs, test stages are skipped (those run on DevDiv instead). +- **`Xamarin.Android-PR`** on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance. Defined in `azure-pipelines.yaml` but its PR trigger is configured in the AZDO UI, not in YAML. It may take a few minutes to start after a push. Use the **pipeline definition name** (from the `definitionName` field) as the label in output — do NOT label them "Public" or "Internal". +When a check shows **"Expected — Waiting for status to be reported"** on GitHub (typically `Xamarin.Android-PR`), it means the pipeline hasn't been triggered yet. This is normal — it's not waiting for the other build, just for AZDO to pick it up. Report it as: "⏳ Not triggered yet — typically starts within a few minutes of a push." + Extract AZDO build URLs from the check `link` fields. Parse `{orgUrl}`, `{project}`, and `{buildId}` from patterns: - `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` - `https://{org}.visualstudio.com/{project}/_build/results?buildId={id}` @@ -219,8 +221,9 @@ Use this format — **one section per AZDO build**, each with its own progress a - Always show elapsed time when `startTime` is available - Show ETA when the build is in progress and historical data is available. If the build has been running longer than the median, say "overdue by ~X min" - Show job counters as "N/Total completed · M running · P waiting" -- If the build hasn't started yet, show "⏳ Build queued, not started yet" -- If only one AZDO build exists (e.g., `.github/`-only PRs don't trigger internal), just show that one +- If the build hasn't started yet, show "⏳ Not triggered yet — typically starts within a few minutes of a push" +- If a check is in "Expected" state with no build URL, it means the AZDO pipeline hasn't picked it up yet — this is normal and not gated on other builds +- If only one AZDO build exists (e.g., `.github/`-only PRs don't trigger `Xamarin.Android-PR`), just show that one **If the build is still running but tests have already failed**, highlight these prominently so the user can start fixing them immediately. Use a note like: @@ -264,7 +267,7 @@ See [references/error-patterns.md](references/error-patterns.md) for dotnet/andr ## Error Handling - **Build in progress:** Still query for failed timeline records AND test runs. Report any early failures alongside the in-progress status. Only offer `gh pr checks --watch` if there are no failures yet. -- **No AZDO build found:** The PR may not have triggered internal CI yet. Report GitHub checks only. +- **Check in "Expected" state (no build URL):** The AZDO pipeline hasn't been triggered yet. This is normal — the two pipelines (`dotnet-android` and `Xamarin.Android-PR`) run independently, not sequentially. Report: "⏳ Not triggered yet — typically starts within a few minutes of a push." Do NOT say it's waiting for the other build. - **Auth expired:** Tell user to run `az login` and retry. - **Build not found:** Verify the PR number/build ID is correct. - **No test runs yet:** The build may not have reached the test phase. Report what's available and note that tests haven't started. From 4c247b8330deaa4777a932bd0b7d6ef8b9f24973 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 26 Mar 2026 15:12:30 +0100 Subject: [PATCH 9/9] ci-status skill: fork detection, per-type ETA, conditional builds Detect fork vs direct PRs via isCrossRepository. Fork PRs get full dotnet-android pipeline (with tests) but no Xamarin.Android-PR. Direct PRs get build-only dotnet-android and full Xamarin.Android-PR. ETA computation filters historical builds by duration profile to match the current PR type. Output shows fork status prominently. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/ci-status/SKILL.md | 60 ++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/.github/skills/ci-status/SKILL.md b/.github/skills/ci-status/SKILL.md index 61383ba363a..49d08331a6d 100644 --- a/.github/skills/ci-status/SKILL.md +++ b/.github/skills/ci-status/SKILL.md @@ -27,21 +27,31 @@ If `az` is not authenticated, stop and tell the user to run `az login`. ### Phase 1: Quick Status (always do this first) -#### Step 1 — Resolve the PR +#### Step 1 — Resolve the PR and detect fork status **No PR specified** — detect from current branch: ```bash -gh pr view --json number,title,url,headRefName --jq '{number,title,url,headRefName}' +gh pr view --json number,title,url,headRefName,isCrossRepository --jq '{number,title,url,headRefName,isCrossRepository}' ``` -```powershell -gh pr view --json number,title,url,headRefName | ConvertFrom-Json +**PR number given** — use it directly: + +```bash +gh pr view $PR --repo dotnet/android --json number,title,url,headRefName,isCrossRepository --jq '{number,title,url,headRefName,isCrossRepository}' ``` If no PR exists for the current branch, tell the user and stop. -**PR number given** — use it directly. +**`isCrossRepository`** tells you whether the PR is from a fork: +- `true` → **fork PR** (external contributor) +- `false` → **direct PR** (team member, branch in dotnet/android) + +This matters for CI behavior: +- **Fork PRs:** `Xamarin.Android-PR` does NOT run. `dotnet-android` runs the full pipeline including tests. +- **Direct PRs:** `Xamarin.Android-PR` runs the full test suite. `dotnet-android` skips test stages (build-only) since tests run on DevDiv instead. + +Highlight the fork status in the output so the user understands which checks to expect. #### Step 2 — Get GitHub check status @@ -59,12 +69,18 @@ Note which checks passed/failed/pending. The `link` field contains the AZDO buil #### Step 3 — Get Azure DevOps build status (repeat for EACH build) There are typically **two separate AZDO builds** for a dotnet/android PR. They run **independently** — neither waits for the other: -- **`dotnet-android`** on `dev.azure.com/dnceng-public` — compiles on Linux, macOS, Windows. Defined in `azure-pipelines-public.yaml` with an explicit `pr:` trigger. For non-fork PRs, test stages are skipped (those run on DevDiv instead). -- **`Xamarin.Android-PR`** on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance. Defined in `azure-pipelines.yaml` but its PR trigger is configured in the AZDO UI, not in YAML. It may take a few minutes to start after a push. +- **`dotnet-android`** on `dev.azure.com/dnceng-public` — Defined in `azure-pipelines-public.yaml` with an explicit `pr:` trigger. + - **Fork PRs:** runs the full pipeline including build + tests (since `Xamarin.Android-PR` won't run for forks). + - **Direct PRs:** runs **build-only** — test stages are auto-skipped because those run on DevDiv instead. This means the `dotnet-android` build will be significantly shorter for direct PRs. +- **`Xamarin.Android-PR`** on `devdiv.visualstudio.com` — full test suite, MAUI integration, compliance. Defined in `azure-pipelines.yaml` but its PR trigger is configured in the AZDO UI, not in YAML. + - **Fork PRs:** does NOT run at all (no access to internal resources). + - **Direct PRs:** runs the full test matrix. May take a few minutes to start after a push. Use the **pipeline definition name** (from the `definitionName` field) as the label in output — do NOT label them "Public" or "Internal". -When a check shows **"Expected — Waiting for status to be reported"** on GitHub (typically `Xamarin.Android-PR`), it means the pipeline hasn't been triggered yet. This is normal — it's not waiting for the other build, just for AZDO to pick it up. Report it as: "⏳ Not triggered yet — typically starts within a few minutes of a push." +When a check shows **"Expected — Waiting for status to be reported"** on GitHub (typically `Xamarin.Android-PR`): +- **For direct PRs:** the pipeline hasn't been triggered yet — this is normal, it's not waiting for the other build, just for AZDO to pick it up. Report it as: "⏳ Not triggered yet — typically starts within a few minutes of a push." +- **For fork PRs:** `Xamarin.Android-PR` will NOT run. Report: "⏳ Will not run — fork PRs don't trigger the internal pipeline." Extract AZDO build URLs from the check `link` fields. Parse `{orgUrl}`, `{project}`, and `{buildId}` from patterns: - `https://dev.azure.com/{org}/{project}/_build/results?buildId={id}` @@ -114,22 +130,29 @@ Check `issues` arrays first — they often contain the root cause directly. #### Step 3a — Estimate completion time per build (when build is in progress) -Use the `definitionId` from the build to query recent successful builds of the **same pipeline definition** and compute the median duration. **Do this separately for each build** — the public and internal pipelines have very different durations. +Use the `definitionId` from the build to query recent successful builds of the **same pipeline definition** and compute the median duration. **Do this separately for each build** — the pipelines have very different durations. + +**Important:** The `dotnet-android` pipeline duration varies significantly based on whether the PR is from a fork: +- **Direct PRs:** `dotnet-android` runs build-only (tests skipped) — typically much shorter (~1h 45min) +- **Fork PRs:** `dotnet-android` runs the full pipeline with tests — typically much longer + +To get accurate ETAs, filter historical builds to match the current PR type. You can approximate this by looking at the **job count** of the current build vs historical builds — build-only runs have ~3 jobs while full runs have many more. Alternatively, compare the historical durations and pick the ones that are similar in magnitude to what you'd expect for the current build type. ```bash az devops invoke --area build --resource builds \ --route-parameters project=$PROJECT \ --org $ORG_URL \ - --query-parameters "definitions=$DEF_ID&statusFilter=completed&resultFilter=succeeded&\$top=5" \ + --query-parameters "definitions=$DEF_ID&statusFilter=completed&resultFilter=succeeded&\$top=10" \ --query "value[].{startTime:startTime, finishTime:finishTime}" \ --output json 2>&1 ``` **Compute ETA:** 1. For each recent build, calculate `duration = finishTime - startTime` -2. Compute the **median** duration (more robust than average against outliers) -3. `ETA = startTime + medianDuration` -4. Present as: "ETA: ~14:30 UTC (median of last 5 runs: ~2h 15min)" +2. Filter to builds with similar duration profile (short ~1-2h for build-only, long ~3h+ for full runs) matching the current PR type +3. Compute the **median** duration of the filtered set (more robust than average against outliers) +4. `ETA = startTime + medianDuration` +5. Present as: "ETA: ~14:30 UTC (typical for direct PRs: ~1h 45min)" If `startTime` is null (build hasn't started yet), skip the ETA and say "Build queued, not started yet". If the build already completed, skip the ETA and show the actual duration instead. @@ -176,6 +199,7 @@ Use this format — **one section per AZDO build**, each with its own progress a ``` # CI Status for PR #NNNN — "PR Title" +🔀 **Direct PR** (branch in dotnet/android) — or 🍴 **Fork PR** (external contributor) ## GitHub Checks | Check | Status | @@ -184,7 +208,8 @@ Use this format — **one section per AZDO build**, each with its own progress a ## dotnet-android [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress -⏱️ Running for **12 min** · ETA: ~15:15 UTC (typical: ~1h 45min) +ℹ️ Build-only (tests run on Xamarin.Android-PR for direct PRs) — or ℹ️ Full pipeline with tests (fork PR) +⏱️ Running for **12 min** · ETA: ~15:15 UTC (typical for direct PRs: ~1h 45min) 📊 Jobs: **0/3 completed** · 1 running · 2 waiting | Job | Status | @@ -195,6 +220,7 @@ Use this format — **one section per AZDO build**, each with its own progress a ## Xamarin.Android-PR [#BuildId](link) **Result:** ✅ Succeeded / ❌ Failed / 🟡 In Progress +— or for fork PRs: ⏳ **Will not run** — fork PRs don't trigger this pipeline ⏱️ Running for **42 min** · ETA: ~15:45 UTC (typical: ~2h 30min) 📊 Jobs: **18/56 completed** · 6 running · 32 waiting @@ -218,12 +244,14 @@ Use this format — **one section per AZDO build**, each with its own progress a ``` **Progress section guidelines:** +- Always show fork status (🔀 Direct PR / 🍴 Fork PR) at the top — it determines which builds run and their expected durations +- For `dotnet-android`, note whether it's build-only (direct PR) or full pipeline (fork PR) +- For `Xamarin.Android-PR` on fork PRs, don't try to query it — just report "Will not run" - Always show elapsed time when `startTime` is available - Show ETA when the build is in progress and historical data is available. If the build has been running longer than the median, say "overdue by ~X min" - Show job counters as "N/Total completed · M running · P waiting" - If the build hasn't started yet, show "⏳ Not triggered yet — typically starts within a few minutes of a push" -- If a check is in "Expected" state with no build URL, it means the AZDO pipeline hasn't picked it up yet — this is normal and not gated on other builds -- If only one AZDO build exists (e.g., `.github/`-only PRs don't trigger `Xamarin.Android-PR`), just show that one +- If a check is in "Expected" state with no build URL on a direct PR, the AZDO pipeline hasn't picked it up yet — this is normal and not gated on other builds **If the build is still running but tests have already failed**, highlight these prominently so the user can start fixing them immediately. Use a note like: