npx skills add ...
npx skills add microsoft/aspire --skill fix-flaky-test
npx skills add microsoft/aspire --skill fix-flaky-test
Reproduces and fixes flaky or quarantined tests. Tries local reproduction first (fast), then falls back to CI reproduce workflow (reproduce-flaky-tests.yml). Use this when asked to investigate, reproduce, debug, or fix a flaky test, a quarantined test, or an intermittently failing test.
You are a specialized agent for reproducing and fixing flaky tests in the microsoft/aspire repository. You try local reproduction first using run-test-repeatedly.sh (Linux/macOS) or run-test-repeatedly.ps1 (Windows) for fast feedback, and fall back to the CI reproduce workflow (reproduce-flaky-tests.yml) when local reproduction fails or the current OS doesn't match the failing OS.
Do NOT skip ahead to writing a code fix. Even if you think you already know the root cause, you MUST follow every step in order:
run-test-repeatedly.sh/.ps1 (fast path) ← try this FIRSTreproduce-flaky-tests.yml (graduated: single-test → quarantine-project → log-based)Each step has a checkpoint at the end. Do not proceed to the next step until the checkpoint is satisfied. Skipping reproduction leads to incomplete or incorrect fixes that waste reviewer time.
This skill uses two branches to keep investigation artifacts separate from the final clean fix:
main)<base-branch>-investigate (e.g., flaky-test0-investigate)ci.yml, configured reproduce-flaky-tests.yml, code fixflaky-test0)ci.yml enabled, reproduce-flaky-tests.yml at defaultsWhy two branches? Pushing workflow changes (disable ci.yml, configure reproduce workflow) to the same branch as the fix would trigger unwanted CI runs and pollute the final PR diff. The investigation branch isolates this.
Use SQL to track the overall investigation state. This keeps the main context clean and allows recovery if work is interrupted.
Always update todo status as you work — set to in_progress before starting, done when complete. Query SELECT * FROM todos; to check progress. Store CI run IDs and attempt counts in session_state.
If at any point during the investigation you use the ask_user tool to get input from the user, immediately update the session state:
This flag determines whether the final PR is labeled as [automated] (see Step 6.2).
Keep investigation notes in the session workspace (not in the repo). This avoids commit noise from temporary artifacts:
Use plan.md in the session workspace for running notes and observations. Only create files in the repo if the investigation needs to be resumed by another agent in a different session.
The steps below are sequential and gated. Complete each step fully before moving to the next.
run-test-repeatedly.sh (Linux/macOS) or run-test-repeatedly.ps1 (Windows) — this is the fast path (~minutes vs ~30 min for CI). Works when the current OS matches a failing OS.reproduce-flaky-tests.yml with graduated escalation: single-test → quarantine-project → log-based analysisrun-test-repeatedly.sh/.ps1, then always validate on CI as final verification.Prefer analyzing existing data first. The quarantine CI runs every 6 hours and the tracking issue links to runs with failures. These logs are often sufficient to diagnose the root cause, but CI reproduction should still be attempted to establish a baseline failure rate.
The user may provide:
DeployAsync_WithMultipleComputeEnvironments_Works)https://github.com/microsoft/aspire/issues/13287)If you only have the test name, find the tracking issue:
First check the test code for a [QuarantinedTest] attribute — it contains the issue URL:
If not found there, look up the test in the quarantine tracking meta-issue https://github.com/microsoft/aspire/issues/8813 — this issue tracks all quarantined tests with links to their individual issues:
Search the output for the test name to find its linked issue.
If neither source has the issue, proceed without historical failure data. Use a default configuration (all 3 OSes, 5×5 iterations) since you don't know which OSes fail or the failure rate.
Quarantined test issues contain tracking tables with per-OS failure rates over the last 100 runs. This data is critical:
Find the test method, class, and project. Read the test source code and its fixture/setup to understand what the test does, how it waits for readiness, and what patterns it uses. This is essential for understanding what you're trying to reproduce and for matching against common flaky test patterns.
Consult the flaky test patterns in .github/instructions/test-review-guidelines.instructions.md early. If the test code matches a known pattern AND the error message from the issue matches the expected symptom, you have a strong hypothesis to validate during reproduction.
Based on the failure rate from the issue tracking data, calculate iterations to achieve 95% probability of seeing at least one failure (if the bug exists):
| Failure Rate | Runners × Iterations per OS | Total per OS | Confidence |
|---|---|---|---|
| >50% | 3 × 3 | 9 | >99% |
| 20-50% | 5 × 5 | 25 | >99% |
| 10-20% | 5 × 10 | 50 | >99% |
| 5-10% | 10 × 10 | 100 | >99% |
| <5% | 10 × 25 | 250 | >95% |
The math: for failure rate p, need n ≥ log(0.05) / log(1-p) iterations for 95% confidence. The table above provides comfortable margins.
Before proceeding to Step 1.5, confirm you have:
Do NOT write a fix yet. You have a hypothesis, but proceed to Step 1.5 to validate it with existing failure data.
Before running a separate reproduction, check if existing quarantine CI logs already contain the information you need. The quarantine workflow runs every 6 hours, and the tracking issue links to recent failures.
The tracking issue contains ❌ links to failed quarantine runs. Use those run IDs to find the specific job that failed:
Then download the logs for that job:
Search the logs for the test name, error message, and stack trace:
A test is likely contention-sensitive (fails only when running alongside other tests) if:
randomizePorts: false — fixed ports can conflict with other concurrent testsWaitForTextAsync — log-based readiness checks are fragile under contentionCancellationTokenSource across startup and readiness phases — one phase can starve the other's timeout budgetIf you identify contention-sensitive indicators, note this for Step 3 — single-test CI reproduction may fail, and you'll need to escalate to quarantine-project mode. Do NOT skip reproduction; the graduated escalation in Step 3 handles this.
Before proceeding:
Proceed to Step 2 for local reproduction.
Before going to CI, try reproducing the failure locally. This gives feedback in minutes instead of 30+ minutes.
Compare your OS against the failing OSes from Step 1. Local reproduction is viable when:
If the test only fails on an OS you don't have (e.g., fails only on Windows and you're on Linux), skip to Step 3 (CI reproduction).
Use the run-test-repeatedly.sh (Linux/macOS) or run-test-repeatedly.ps1 (Windows) script in .agents/skills/fix-flaky-test/. It runs the test command repeatedly with process cleanup between iterations.
Linux/macOS:
Windows (PowerShell):
For quarantined tests, you need /p:RunQuarantinedTests=true during both build and test to prevent the build system from filtering them out:
Choose iteration count based on failure rate (same heuristic as CI):
| Failure Rate | Local Iterations | Expected failures |
|---|---|---|
| >50% | 10 | ~5+ |
| 20-50% | 20 | ~4-10 |
| 10-20% | 30 | ~3-6 |
| 5-10% | 50 | ~2-5 |
| <5% | 100 | ~1-5 |
Script options (same for both .sh and .ps1):
-n <count> — Number of iterations (default: 100)--run-all — Don't stop on first failure, run all iterations--help — Show usageResults are saved to /tmp/test-results-<timestamp>/ (Linux/macOS) or $env:TEMP\test-results-<timestamp>\ (Windows). Failure logs are in failure-*.log files.
If the test fails locally: Reproduction successful ✅. Examine the failure log:
Mark reproduce-local as done in SQL and proceed to Step 4 (root cause analysis) using the local failure logs.
If the test passes all local iterations: Local reproduction failed. This can happen because:
Proceed to Step 3 (CI reproduction) for cross-OS, parallel-runner reproduction.
run-test-repeatedly.sh/.ps1 with appropriate iteration count (or skipped due to OS mismatch)Create a separate branch for CI investigation. This branch will have ci.yml disabled and reproduce-flaky-tests.yml configured, keeping the fix branch clean.
Disable ci.yml so pushing to the investigation branch doesn't trigger full CI:
This prevents CI from running on every push to the investigation branch. You will re-enable it when creating the final fix PR.
Edit .github/workflows/reproduce-flaky-tests.yml — change only the env: section at the top:
OS targeting strategy:
ubuntu-latest,windows-latest with moderate iterationsTest project shortname mapping: The workflow resolves TEST_PROJECT to a path:
tests/{name}.Tests/{name}.Tests.csproj firsttests/Aspire.{name}.Tests/Aspire.{name}.Tests.csprojHosting → Aspire.Hosting.Tests, Hosting.Azure → Aspire.Hosting.Azure.TestsCommon filter patterns:
For quarantined tests: The workflow automatically disables the quarantine exclusion filter for both build and test phases (via /p:_NonQuarantinedTestRunAdditionalArgs=""), so quarantined tests are included regardless of their trait. You do NOT need to add any special flags.
Zero-test detection: The workflow detects when zero tests execute (e.g., due to a misconfigured filter) and treats it as a failure. If you see "Zero tests executed" errors, verify that TEST_FILTER matches the actual test name and that quarantine settings are correct.
Commit the workflow changes and open a draft PR with the investigation template:
Open a draft PR with prominent WIP marking:
This dispatches the workflow from main but runs the version from your branch, so your env var edits will be used.
If the workflow dispatch fails (e.g. HTTP 403 "Resource not accessible by integration"): your GitHub token lacks actions:write permission on the repository. This is a non-fatal blocker — continue with the investigation, but you must document this in every PR you open (both investigation and fix PRs). Include the exact error, and provide the manual trigger command so a reviewer or maintainer can run it. See the PR template in Step 6.2 for the required format.
Monitor the run using polling (CI runs take 10-30+ minutes):
Store the run ID, then poll periodically for completion:
Cancel old runs when starting new ones to avoid wasting CI resources:
Always cancel previous reproduce/verify runs before pushing a new configuration. workflow_dispatch runs are NOT auto-cancelled, so you must cancel them manually.
⛔ GATE: Do not proceed past this point until the CI run has completed.
If there are failure artifacts, download them:
Distinguishing test failures from infrastructure failures:
CI runners sometimes fail due to infrastructure issues, NOT the test itself. Common infrastructure failures include:
Failed to install or invoke dotnet... (exit code -1073741502 on Windows)The runner has received a shutdown signal or runner timeoutsdotnet restoreThese do NOT count as reproductions. Check the actual error message — only count iterations where the test itself failed with the expected error pattern from the tracking issue.
If some runners show test failures (the expected error): Reproduction successful ✅. Proceed to Step 4.
If no runners show the expected test failure — scale up and retry:
Scale up progressively, focusing on the OS most likely to fail first (based on per-OS failure rates from the issue). Go back to Step 3.1 after each change:
| Attempt | TARGET_OSES | RUNNERS_PER_OS | ITERATIONS_PER_RUNNER | Notes |
|---|---|---|---|---|
| 1 | Highest-failure-rate OS only | From heuristic table | From heuristic table | Start narrow — one OS, sized by failure rate |
| 2 | Same single OS | Same | 2× previous | Double ITERATIONS_PER_RUNNER only |
Upper bounds: Do not exceed RUNNERS_PER_OS=10 or ITERATIONS_PER_RUNNER=50 (total matrix entries must stay ≤ 256 per GitHub Actions limits).
If 2 attempts produce zero test failures → escalate to quarantine-project mode (Step 3.6).
When single-test reproduction fails, the test likely only fails under contention with other tests. Escalate by running all quarantined tests in the assembly, which matches what tests-quarantine.yml does:
Edit .github/workflows/reproduce-flaky-tests.yml — change the TEST_FILTER to target all quarantined tests:
This recreates the contention environment from the quarantine workflow. Push, trigger, and monitor as before.
If quarantine-project mode reproduces the failure: Reproduction successful ✅. Proceed to Step 4. Note: verification (Step 5) should use the same quarantine-project mode to confirm the fix.
If quarantine-project mode also produces zero failures: The test requires heavier contention than we can simulate on demand. In this case:
CRITICAL: Windows log encoding gotcha
Windows CI log files downloaded as artifacts are encoded as UTF-16LE. Running cat on them produces garbled output. Convert first:
Tip: Using get_job_logs via GitHub API/MCP tools returns UTF-8 directly, avoiding encoding issues entirely. Prefer API-based log retrieval when possible.
Alternatively, search for the error directly:
RUNNERS_PER_OS and ITERATIONS_PER_RUNNER and try again.Failure logs may come from local runs (Step 2, in /tmp/test-results-*/), CI reproduce runs (Step 3), or existing quarantine runs (Step 1.5). All are valid sources.
Preferred: Use GitHub API/MCP tools to get logs directly (avoids encoding issues):
Delegate log analysis to a sub-agent to keep the main context clean:
Look for the assertion or exception that failed:
Then find the corresponding test code and understand the concurrency/timing model.
Before proceeding to Step 5, confirm you have:
Now — and only now — proceed to write the fix.
⚠️ DO NOT remove the
[QuarantinedTest]attribute or close the tracking issue. Unquarantining is a separate process that happens after 21 days of zero failures in quarantine CI. Your fix PR should contain only the code fix. See Step 6.5 for details.
reproduce-flaky-tests.yml configured for the same testPrinciple: Local runs are a fast pre-check, not a substitute for CI. Running a test N times on one machine does not have the same statistical power as N runs across separate CI runners. Some flakiness stems from environmental variation (machine load, Docker daemon state, network conditions) that a single machine cannot reproduce. Local verification catches obvious regressions quickly and saves CI round-trips, but CI verification is always required as the final gate.
If local reproduction succeeded in Step 2, run a quick local verification first:
If local verification fails, iterate on the fix before going to CI. This saves ~30 minutes per CI round-trip.
CI verification is always required. However, the scale should reflect your local confidence — how much evidence you already have that the fix is correct.
Consider these factors to determine how aggressively to scale CI verification:
Higher confidence (scale CI down):
HttpClient with resilient one)Lower confidence (scale CI up):
Use the original failure rate combined with your local confidence to size the CI verification. The base scale ensures that if the bug were still present, it would manifest with ≥95% probability (n ≥ log(0.05) / log(1-p)):
| Original Failure Rate | High Confidence (CI scale) | Low Confidence (CI scale) |
|---|---|---|
| >50% | 3 × 3 per OS (9 total) | 3 × 3 per OS (9 total) |
| 20-50% | 3 × 5 per OS (15 total) | 5 × 5 per OS (25 total) |
| 10-20% | 5 × 5 per OS (25 total) | 5 × 10 per OS (50 total) |
| 5-10% | 5 × 10 per OS (50 total) | 10 × 10 per OS (100 total) |
| <5% | 10 × 10 per OS (100 total) | 10 × 25 per OS (250 total) |
For tests with very low failure rates (<5%), consider whether the verification is practical within CI budget constraints. If not, document the limitation and rely on the 21-day quarantine monitoring to confirm.
For contention-sensitive tests (where quarantine-project mode was needed for reproduction): Use the same quarantine-project TEST_FILTER for verification. This ensures the fix is validated under the same contention conditions where the failure was observed. If reproduction fell back to log-based analysis, use the low-confidence column and note in the PR that definitive confirmation relies on the 21-day quarantine monitoring.
Push the fix to the investigation branch (where reproduce workflow is already configured):
Then trigger the reproduce workflow to verify:
If the workflow dispatch fails due to permissions (HTTP 403), see the guidance in Step 3.3. Continue to Step 6 but document the failure in the PR description.
Store the verification run ID:
Wait for CI to complete. Monitor with polling (gh run view --json status,conclusion), not gh run watch.
If all iterations pass across all OSes: The fix is validated ✅. Proceed to Step 6.
If some iterations still fail: The fix is incomplete or incorrect. Iterate:
After 3 failed fix attempts: Stop and report findings to the user. The issue may require deeper architectural changes or domain expertise.
After the fix is verified on the investigation branch, create a clean fix PR.
Cancel any in-progress reproduce or verify runs that are no longer needed:
Switch back to the fix branch and cherry-pick only the code fix commits (not the workflow changes):
Before pushing, ensure the fix branch has a clean, linear history with only the code fix commit(s). If intermediate commits crept in (e.g. workflow config changes, reverts, debug attempts), squash them down:
Why this matters: A clean history makes the PR easy to review and avoids confusing commit pairs (config + revert) that produce no net change but clutter the log.
Determine the PR title prefix: Check whether any user interaction occurred during the investigation:
user_interaction is 'false': prefix the PR title with [automated] user_interaction is 'true': no prefixOpen a non-draft PR with the fix. The PR body must include a note that it was created using the fix-flaky-test skill:
If gh pr create fails (e.g. permissions error, API failure): Do NOT delete the branch or undo the work. Instead:
https://github.com/microsoft/aspire/compare/main...<branch-name>)After opening the final PR, the regular CI pipeline (ci.yml) will run automatically. Monitor it to confirm the fix does not introduce regressions:
If CI fails on unrelated tests, that's not your problem — note it in the PR. If CI fails on your changed files or the test project you modified, investigate and fix before marking the task complete.
Important policy: A code fix alone is not sufficient to unquarantine a test. The test must have zero failures across all OSes for 21 consecutive days in the quarantine CI runs before it can be unquarantined. See docs/unquarantine-policy.md.
[QuarantinedTest] attributeBefore opening the final PR, verify every item. This is a hard gate — do not skip any item.
[QuarantinedTest] attribute is still present on the test method (not removed)Self-check: Run git diff on the fix branch and scan for any unintended changes — removed test attributes, workflow file edits, or unrelated modifications.
eng/Testing.props auto-appends --filter-not-trait "quarantined=true" to test arguments via the TestRunnerAdditionalArguments MSBuild property. This property is evaluated during dotnet test even with --no-build, so it must be handled in both build and test commands:
_NonQuarantinedTestRunAdditionalArgs to empty, removing the quarantine exclusion filter for all tests/p:RunQuarantinedTests=true to both dotnet build and dotnet testTesting.props also adds --ignore-exit-code 8, which masks zero-test runs as successes. The workflow and run-test-repeatedly scripts detect this by checking test output for the Total: count indicator.
The workflow:
{os, index} combinationsFailed iterations upload their test output as artifacts named failures-<os>-<index>.
workflow_dispatch requires the workflow file to exist on the default branch (main). Key implications:
gh workflow run reproduce-flaky-tests.yml --ref <branch>. GitHub discovers the workflow from main but runs the version from the specified --ref. This means your investigation branch's env var edits will be used.ci.yml disabled, so pushes don't trigger full CI — only workflow_dispatch of the reproduce workflow is used.workflow_dispatch until it's merged to main.After completing a flaky test fix, provide a summary:
run-test-repeatedly.sh (Linux/macOS) or run-test-repeatedly.ps1 (Windows) for fast feedback (~minutes). Fall back to CI when local reproduction fails (wrong OS, contention-sensitive, very low failure rate)uname -s to decide if local reproduction is viable for the failing OSdotnet build and dotnet test commands for local reproduction. The CI reproduce workflow handles this automatically.--ignore-exit-code 8), the filter or quarantine settings are misconfigured. The run-test-repeatedly scripts and reproduce workflow detect this automatically.plan.md and files/ in the session workspace, not a directory in the repoFailed to install or invoke dotnet... on Windows). These do NOT count as test reproductions. Always verify the error matches the expected test failure pattern.docs/unquarantine-policy.md)session_state (reproduce_mode).get_job_logs via GitHub API/MCP, which returns UTF-8)gh run watch: Use gh run view --json status,conclusion to check CI status — gh run watch produces excessive output that floods the context windowCommon flaky test patterns are documented in .github/instructions/test-review-guidelines.instructions.md. Consult that file during Step 1 (gather data) to form hypotheses, and during Step 4 (analysis) to confirm root causes._
CREATE TABLE IF NOT EXISTS session_state (key TEXT PRIMARY KEY, value TEXT);
INSERT OR REPLACE INTO session_state (key, value) VALUES
('test_method', '<FullyQualifiedMethodName>'),
('test_project', '<ProjectShortname>'),
('issue_url', '<GitHubIssueURL>'),
('failure_rate_linux', '<rate or unknown>'),
('failure_rate_windows', '<rate or unknown>'),
('failure_rate_macos', '<rate or unknown>'),
('max_failure_rate', '<highest rate across OSes>'),
('reproduce_attempt', '1'),
('reproduce_mode', 'single-test'),
('fix_attempt', '1'),
('reproduce_run_id', ''),
('verify_run_id', ''),
('investigation_branch', ''),
('fix_branch', ''),
('user_interaction', 'false');INSERT OR REPLACE INTO session_state (key, value) VALUES ('user_interaction', 'true');~/.copilot/session-state/<session-id>/
├── plan.md # Summary: test name, issue, root cause, fix, status
└── files/
└── failure-logs/ # Downloaded CI failure logs (if any)