| name | ci-pipeline-monitor |
| description | Monitors .NET runtime CI test pipelines on Azure DevOps. Use this skill when asked to monitor CI pipeline test results, triage CI test failures across ADO pipelines, or generate CI test monitoring reports.
|
CI Pipeline Monitor
Overview
This skill automates monitoring CI test pipelines on Azure DevOps
(dnceng-public/public), triaging failures, and coordinating with GitHub
dotnet/runtime issue tracking.
Pipelines to Monitor
The list of pipelines and their cached definition IDs is maintained in
pipelines.md in this skill directory.
Architecture
Deterministic steps are scripted. Agent does triage.
- Python scripts (
scripts/) handle all deterministic work: DB setup,
build fetching, test failure extraction (including errorMessage and
stackTrace from the ADO API), Helix log downloading, and report
generation.
- Agent handles all non-deterministic work: reading console log files for
failures where the API returned no useful error (crashes, timeouts),
enriching/completing error messages and stack traces, classifying failures,
grouping by root cause, searching GitHub for matching issues, writing
analysis, and populating the triage tables in
monitor.db.
Scripts
Use these scripts — do NOT write ad-hoc replacements. Do NOT create new files
in scripts/ — only the committed scripts below belong there. For ad-hoc
queries during triage (e.g., DB lookups, grouping), prefer python -c '...'
inline. If a query is too complex for inline (escaping issues, multi-line),
write a temp file under temp/ (e.g., temp/_query.py). The temp/
directory is gitignored; the user will clean it up when no longer needed.
| Script | Step | What it does |
|---|
setup_and_fetch_builds.py | 2 | Creates monitor.db (including test_results table), fetches latest build for every pipeline, populates pipelines table. |
extract_failed_tests.py | 3 | Reads failing pipelines from DB. Calls AzDO Test Results API for each failing build. INSERTs one row per failed test method into test_results (test_name, run_name, pipeline_name, Helix info, console URL, error_message, stack_trace from API). Strips .WorkItemExecution suffix. Skips generic "Helix Work Item failed" messages (stores empty — agent fills from console log). Requires ADO_TOKEN env var or az cli. |
fetch_helix_logs.py | 3 | Fetches Helix console logs, saves full log files to helix-logs/ directory, UPDATEs each test_results row with exit_code and console_log_path. No auth needed. |
validate_results.py | 5 | Validates monitor.db completeness and integrity. 24 checks: data completeness, referential integrity, data quality, content accuracy, debug log completeness. Exits 1 on failure. |
generate_report.py | 6 | Reads monitor.db, generates report to logs/ directory. Pure formatting — no judgment. Run only after DB validation passes. |
End-to-end pipeline:
cd .github/skills/ci-pipeline-monitor
pip install requests
python scripts/setup_and_fetch_builds.py --pipelines pipelines.md --db scripts/monitor.db
python scripts/extract_failed_tests.py --db scripts/monitor.db
python scripts/fetch_helix_logs.py --db scripts/monitor.db
python scripts/validate_results.py --db scripts/monitor.db --pipelines pipelines.md --log logs/ci-pipeline-monitor-*.log
python scripts/generate_report.py --db scripts/monitor.db [--validation-warnings]
Database Schema
Created by setup_and_fetch_builds.py. Populated by scripts (Steps 2-3) and
agent (Step 4). Validated by validate_results.py (Step 5). Read by
generate_report.py (Step 6).
CREATE TABLE pipelines (
name TEXT PRIMARY KEY,
build_id INTEGER,
build_number TEXT,
result TEXT NOT NULL,
skip_reason TEXT
);
CREATE TABLE test_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pipeline_name TEXT NOT NULL,
build_id INTEGER NOT NULL,
run_name TEXT NOT NULL,
test_name TEXT NOT NULL,
helix_job_id TEXT,
helix_work_item TEXT,
console_log_url TEXT,
exit_code INTEGER,
console_log_path TEXT,
error_message TEXT,
stack_trace TEXT,
failure_id INTEGER,
FOREIGN KEY (failure_id) REFERENCES failures(id)
);
CREATE TABLE failures (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
scope TEXT,
test_name TEXT NOT NULL,
work_item TEXT,
failure_category TEXT,
exit_codes TEXT,
failing_since_date TEXT,
failing_since_build TEXT,
console_log_url TEXT,
source_test_result_id INTEGER,
error_message TEXT,
stack_trace TEXT,
summary TEXT,
analysis TEXT,
github_issue_number INTEGER,
github_issue_url TEXT,
github_issue_state TEXT,
github_issue_assigned TEXT,
labels TEXT,
milestone TEXT DEFAULT '11.0.0'
);
CREATE TABLE failure_pipelines (
failure_id INTEGER NOT NULL REFERENCES failures(id),
pipeline_name TEXT NOT NULL,
build_id INTEGER,
build_number TEXT,
PRIMARY KEY (failure_id, pipeline_name)
);
CREATE TABLE failure_tests (
failure_id INTEGER NOT NULL REFERENCES failures(id),
pipeline_name TEXT NOT NULL,
run_name TEXT NOT NULL,
test_name TEXT NOT NULL
);
Workflow
Debug Log
All output goes in logs/ (sibling of scripts/).
- Debug Log (
logs/ci-pipeline-monitor-<timestamp>.log) — always generated.
⚠️ Write incrementally by appending after each API call and decision.
Do NOT compose the log from memory at the end of the run — this defeats
the crash-recovery purpose. If the process crashes mid-run, the log must
contain everything up to the crash point. Use file append operations
(Python open(..., 'a') or PowerShell Add-Content) to write each log
entry immediately after the action it describes. Follow
log-template.md. Log every API call URL + response
summary, every decision with reasoning, timestamps, and errors.
- Test Report (
logs/test-report-<timestamp>.md) — always generated
via generate_report.py.
Step 0: Prerequisites (agent)
Run before anything else. See references/prerequisites.md
for full details.
pip install requests
- Ensure
ADO_TOKEN env var is set (required for Step 3).
⚠️ Do NOT proceed to Step 3 without a valid ADO_TOKEN. The Test Results
API returns 203 (sign-in HTML) without auth, even on dnceng-public.
Step 1: Resolve Pipeline Definitions (agent)
Compare the Pipeline Details table (source of truth) against the Cached
Definition ID Mapping table in pipelines.md:
-
For each pipeline in Pipeline Details that is not marked Private or
skip in its Notes column:
-
For pipelines in the Cached Mapping table that are no longer in Pipeline
Details, leave them (stale rows are harmless — the script only processes
pipelines present in the Cached Mapping table).
Do NOT re-resolve IDs that are already populated with a numeric value.
Step 2: Fetch Latest Builds (deterministic — scripted)
python scripts/setup_and_fetch_builds.py --pipelines pipelines.md --db scripts/monitor.db
Creates DB, fetches latest build per pipeline, populates pipelines table,
outputs failing build IDs.
Step 3: Extract Failed Tests and Fetch Logs (deterministic — scripted)
python scripts/extract_failed_tests.py --db scripts/monitor.db
python scripts/fetch_helix_logs.py --db scripts/monitor.db
Extracts individual failed test methods and downloads their full Helix console
logs to disk.
⚠️ Every individual failure must be INSERT'd into test_results immediately.
extract_failed_tests.py: inserts one row per failed test method (test_name,
run_name, pipeline_name, helix_job_id, helix_work_item, console_log_url,
error_message, stack_trace from the ADO API). The API provides useful
error/stack for most xUnit assertion failures. For crashes and timeouts,
the API returns a generic "Helix Work Item failed" message — these are
stored as empty so the agent can extract the real error from the console log.
fetch_helix_logs.py: downloads the full console log to helix-logs/
(a separate directory — NOT mixed with logs/) and UPDATEs the
corresponding test_results row with exit_code and console_log_path.
Uses console_log_path IS NULL as the sentinel for unprocessed rows.
- After Step 3,
test_results contains the complete raw inventory of every
failure with its exit code, a path to the full console log on disk, and
API-provided error/stack where available. failure_id is NULL — it is
populated by the agent in Step 4.
Step 4: Triage Failures (agent — non-deterministic)
See references/triage-workflow.md for full instructions.
⚠️ INSERT into failures table immediately after triaging each failure group.
Step 5: Validate DB (deterministic — scripted)
python scripts/validate_results.py --db scripts/monitor.db --pipelines pipelines.md --log logs/ci-pipeline-monitor-<timestamp>.log
Runs 24 checks across data completeness, referential integrity, data quality,
and content accuracy. Exits 1 on failure.
For the full list of checks, see references/validation-checks.md.
Step 5a: Fix Validation Failures (up to 3 retries)
If any checks fail after Step 5:
-
Read the validator output — each FAIL line includes the specific
test_results IDs, failure IDs, or field names that failed.
-
For each fixable failure (e.g., truncated stack trace, missing
error_message):
- Look up the test_results row in the DB
- Re-read the console log file at
console_log_path
- UPDATE the corrected field in the DB
-
Re-run the validator:
python scripts/validate_results.py --db scripts/monitor.db --pipelines pipelines.md --log <log_path>
-
If failures decreased, repeat from step 1 (up to 3 total retries).
If failures did NOT decrease (same or more), stop retrying.
-
If checks still fail after retries, log each as a WARN in the debug
log with clickable links and move on:
[WARN] Validation error persists after retry — <check description>
Pipeline: [<name> <build_number>](<ado_test_results_tab_url>)
Console Log: [Console Log](<helix_url>)
Field: <field_name>, failure_id=<N>
Stop retrying when failure count stops decreasing or after 3 attempts.
Log remaining WARNs and proceed to report generation. Some failures
(e.g., LLM output truncation) may not be fixable programmatically.
Step 6: Generate Report (deterministic — scripted)
python scripts/generate_report.py --db scripts/monitor.db
python scripts/generate_report.py --db scripts/monitor.db --validation-warnings
Reads DB, outputs report following report-template.md. Only run after
DB validation (Step 5/5a) is complete so the report is generated once.
Step 7: Bisect Regressions (agent — on request)
- Check
failing_since_date/failing_since_build from failures table
- Get commit range between failing and last passing build
- List PRs merged in that range via GitHub MCP server
- Check file overlap with test's source area
- Rank and present top candidates with evidence
Banned Tools and APIs
ado-pipelines_* and ado-testplan_* MCP tools are banned:
ado-testplan_show_test_results_from_build_id returns 1M+ rows and times out.
ado-pipelines_get_builds, get_build_log, get_build_status,
get_build_changes, get_build_log_by_id
- Build Timeline API is banned (
/_apis/build/builds/{id}/timeline):
- reports at work-item level only, silently misses individual test failures. Always use the AzDO Test Results API via
extract_failed_tests.py.
- Use
powershell with requests for any direct API calls.
Allowed Tools
| Step | Tools | Purpose |
|---|
| 0 | powershell | Install dependencies, obtain ADO_TOKEN |
| 1 | powershell, edit | Resolve def IDs via AzDO API, update pipelines.md |
| 2-3 | powershell | Run scripts |
| 4 | powershell, github-mcp-server-search_issues, github-mcp-server-issue_read | Read logs, search GitHub, INSERT failures |
| 5 | powershell | Run validate_results.py |
| 5a | powershell, view | Fix validation failures, re-validate (up to 3 retries) |
| 6 | powershell | Run generate_report.py |
| 7 | github-mcp-server-list_commits, get_commit, search_pull_requests, get_file_contents | Trace regressions |
File I/O tools (view, edit, create, grep, glob) always allowed.
Rules
Extraction
- Every individual failure must be saved to
test_results — this is the
complete inventory. No failure may exist only in JSON output or in memory.
- Never skip failures — cross-check counts against AzDO summary. If results
appear truncated, paginate until all are listed.
- Analyze every failing pipeline — never skip a pipeline or mark it as
"needs investigation" or "expected same failures". Every pipeline must have
confirmed findings from its own test results.
- If a pipeline/API call fails, log a warning and continue — never block the run.
- Use sub-agents for parallel failure extraction — delegate pipeline groups to
separate general-purpose agents via the task tool. Pass the
ADO_TOKEN to
each sub-agent for AzDO Test Results API. Helix API needs no auth.
- Old
failingSince builds may be purged (>90 days). Link to the latest
failed build instead of generating a dead URL.
- AzDO Test Results API requires a bearer token — see Step 0 for how to
obtain and set
ADO_TOKEN. The token is valid ~60 minutes.
- AzDO Builds API and Helix API require NO authentication.
Triage
- Read the FULL console log file — do NOT read only the tail or a partial range.
- Classify using BOTH exit code AND error message — same exit code does
NOT mean the same root cause.
- Do NOT group failures by exit code alone — read the actual error messages.
- For detailed triage workflow, see
references/triage-workflow.md.
- For verbatim copy-paste rules, see
references/verbatim-rules.md.
Bisect
- When bisecting, present evidence — don't guess.