Triage integration test failures
Overview
A failure investigation ends with one of five remedies, not a patch guessed
under time pressure. Classify the failure first. The category decides the
remedy; the wrong category produces a fix that treats a symptom.
REQUIRED BACKGROUND: superpowers:systematic-debugging governs root-cause
work in general. This skill adds the classification step and the toolkit
specific to this repo's CI, agent, and test infrastructure.
Hard rule: delegate the reading
A CI job log, an az monitor/kubectl dump, and an agent log are evidence,
not the deliverable. None of them belongs in the main session.
- Run every command in the toolkit below inside a subagent (Agent tool --
general-purpose or Explore), not in the main session. Give the subagent
the exact command and the exact question ("did this job fail on a
connection error before any agent code ran, or on an assertion diff?");
it returns a short verdict with the one or two evidence lines that prove
it, never the raw log or the full command output.
- This applies even to a "quick look" -- a CI log or agent log runs to
hundreds of KB or has single lines tens of KB wide, and once that text
lands in the main session it is re-billed on every later turn for the
rest of the investigation.
- For agent-log evidence specifically, dispatch a subagent that follows the
analyze-dotnet-agent-logs skill; do not hand-parse the log yourself
even inside that subagent.
- The main session's job is Steps 2-4: collect verdicts, classify, decide,
and record the decision. It holds conclusions, not evidence bytes.
Step 1: is it even a flake?
- Same PR, same test, one run failed and a rerun passed with no code change:
flake. Continue.
- Fails on every run, or only after a real code change: this is a
regression, not a flake. Use superpowers:systematic-debugging instead.
- Check
gh run list for the workflow: has this exact test or job failed
before on unrelated PRs? Count occurrences of the exact signature across
recent scheduled runs, and state the rate as per-run, not per-call -- one
occurrence in 100 runs is not proof of absence. A recurring signature
across unrelated changes is strong evidence of an environmental cause, not
a code bug.
Step 2: classify by evidence, not guess
Before matching a row, trace the failing message or exception string to the
code that emits it, and note whether the assertion sits in the test body or
in fixture setup/teardown (e.g. RemoteApplicationFixture.TestForKnownProblems
is a health check, not the test's own logic) -- this decides which row
applies.
| Category |
Signature |
Evidence to pull |
| Infra / cluster |
Whole class of tests fails identically across every framework/OS variant in the same run; error is a connection, timeout, or account/lock error thrown before any agent code runs |
az monitor metrics list on the AKS node and load balancer; kubectl get pods -n unbounded-services -o wide for restarts/uptime; does a bare retry pass (transient) or fail identically (persistent server state)? |
| Timing / race |
Assertion is a count or an event that "usually" arrives, on a harvest or aggregator cycle; failure shows the right data, late or short by one cycle |
The agent log, via analyze-dotnet-agent-logs -- confirm the exact interleaving (e.g. Seen vs Sent counts) before touching the test |
| Tooling / dependency bug |
Crash signature is inside a third-party tool's native code, not in test or agent code; all tests report Passed, only the collector/runner process dies |
The crash stack (module name, access-violation code); the tool's own changelog/issue tracker for a fix version already in flight |
| External / upstream |
Error surfaces in CI infrastructure setup (runner boot, docker daemon, VM image), not in the app or the agent at all |
Search the CI vendor's own issue tracker (e.g. actions/runner-images) for the exact error string before assuming it's local |
| Product defect, exposed nondeterministically |
The traced assertion is a health check or invariant (fixture setup/teardown), or the emitting code shows a structural hole (missing try/finally, unguarded async race) |
The suspect code path traced above; a repro run that proves the path executed (see Toolkit) |
Never conclude "just flaky" without pulling at least one row's evidence. See
[[feedback_no_guessing]] -- a root cause claim needs a log line, a metric, or
a matching upstream issue behind it, not a hunch. An assertion that exists to
catch a product defect is never reclassified as timing/race and its
threshold is never widened -- trace and fix the defect instead.
Step 3: pick the remedy for the category
| Category |
Remedy |
Do NOT |
| Infra, transient (network blip) |
CI-level retry-once on the job, plus raise the client-side timeout that actually stalled; document as an accepted risk |
Silently widen unrelated timeouts repo-wide |
| Infra, persistent server state (account lock, stuck pod) |
Operational fix (restart the pod/service); decide explicitly whether to harden the exposure, and record that decision even if the answer is "no, too costly" |
Write code to retry around infra state that a retry cannot clear |
| Timing / race in event-harvest assertions |
A race-free wait keyed on the exact asserted evidence (e.g. an AgentLogBase.WaitForMetricAggregateCallCount-style helper polling the real aggregate) |
Bump a harvest-cycle magic number again -- each prior bump on the same test is evidence the race is still there, only the window changed |
| Tooling bug with no correctness impact |
Wait for the upstream version bump already in flight; record the specific version and re-check after it lands |
Change unrelated project settings (e.g. PDB format) as a workaround before confirming the bump doesn't already fix it |
| External/upstream CI regression |
Apply the vendor's own recommended workaround inside the repo's CI composite action/step; link the upstream issue in a comment; remove the workaround once fixed upstream |
Invent a bespoke workaround when the vendor already published one |
| Product defect, exposed nondeterministically |
Trace the code path, file a ticket, leave the check alone; hand off to superpowers:systematic-debugging with the gathered evidence as the starting hypothesis and stop triage |
Reclassify as timing/race, widen the assertion or its timeout, or suppress it to keep CI green |
Step 4: record the decision
Every one of the categories above ends in a decision someone will ask about
again ("why don't we just fix the LB exposure", "did coverlet ever get
bumped"). Capture, in a project memory or handoff note:
- The failure signature and the run/job that showed it.
- The evidence that ruled out the other categories.
- The remedy chosen, and what was explicitly rejected and why.
- Anything left open (a version to watch for, a follow-up not started).
Toolkit quick reference
Each row runs inside a subagent per the hard rule above; the main session
sees only the verdict it returns.
- Both
gh run view --job <id> --log-failed and --log truncate silently
past ~889 lines / 168KB, cutting off mid agent-log dump. For the complete
text: gh api repos/<org>/<repo>/actions/jobs/<jobid>/logs. Copy any log to
a file before a step that clears it -- lost evidence cannot be re-created.
The pristine agent log also survives as CI artifact
integration-test-results-<matrix> (all_solutions.yml uploads
C:\IntegrationTestWorkingDirectory\**\*.log unconditionally).
az monitor metrics list --resource <aks-or-lb-id> --metric <name> --
cluster/LB health at the failure timestamp (MSYS_NO_PATHCONV=1 needed for
the slash-prefixed resource ID in Git Bash).
kubectl get pods -n unbounded-services -o wide -- pod restarts/uptime
after az aks get-credentials.
- analyze-dotnet-agent-logs skill -- agent-side evidence (harvest
timing, Seen/Sent counts, connect sequence) from the test's own log.
- run-integration-tests skill -- reproduce the failing test locally
before trusting a fix. A null result only counts if the code path is
proven to have executed (log entry/exit counts); when the agent itself is
a suspect, rerun once with the agent detached to isolate the confound.
Delegate the run itself, per that skill's own subagent guidance; take back
the summary, not the console output.
Common mistakes
- Treating "all variants failed the same way" as proof of a code bug -- it
is usually the opposite: identical failure across unrelated variants
points at a shared external dependency, not the code under test.
- This pattern covers whole-class failures across unrelated variants only --
a single test failing once, or a fixture health-check assertion firing, is
not covered by it and may be the product-defect row instead.
- Fixing the assertion's timeout/retry count instead of the mechanism that
makes the wait racy. If a similar bump has already happened once on this
test, that is the signal to stop bumping and find the mechanism.
- Closing the investigation without writing down why the infra issue was
not hardened -- the same question comes back next time the flake recurs.
- Reading a CI log or agent log directly in the main session "just this
once" -- delegate it every time; a single raw read is how a multi-step
investigation quietly fills the context window.
1---2name: triage-integration-test-failures3description: Use when a CI integration, unbounded, or container test job fails intermittently, passes on a rerun, fails identically across every matrix variant, or shows an infra-shaped error (connection timeout, account lock, native crash, docker daemon not ready) rather than a clear assertion diff -- the cause may be environmental, tooling, or a real product defect exposed only sometimes. Classify the failure before proposing any fix.4---56# Triage integration test failures78## Overview910A failure investigation ends with one of five remedies, not a patch guessed11under time pressure. Classify the failure first. The category decides the12remedy; the wrong category produces a fix that treats a symptom.1314**REQUIRED BACKGROUND:** superpowers:systematic-debugging governs root-cause15work in general. This skill adds the classification step and the toolkit16specific to this repo's CI, agent, and test infrastructure.1718## Hard rule: delegate the reading1920A CI job log, an `az monitor`/`kubectl` dump, and an agent log are evidence,21not the deliverable. None of them belongs in the main session.2223- Run every command in the toolkit below inside a subagent (Agent tool --24 general-purpose or Explore), not in the main session. Give the subagent25 the exact command and the exact question ("did this job fail on a26 connection error before any agent code ran, or on an assertion diff?");27 it returns a short verdict with the one or two evidence lines that prove28 it, never the raw log or the full command output.29- This applies even to a "quick look" -- a CI log or agent log runs to30 hundreds of KB or has single lines tens of KB wide, and once that text31 lands in the main session it is re-billed on every later turn for the32 rest of the investigation.33- For agent-log evidence specifically, dispatch a subagent that follows the34 **analyze-dotnet-agent-logs** skill; do not hand-parse the log yourself35 even inside that subagent.36- The main session's job is Steps 2-4: collect verdicts, classify, decide,37 and record the decision. It holds conclusions, not evidence bytes.3839## Step 1: is it even a flake?4041- Same PR, same test, one run failed and a rerun passed with no code change:42 flake. Continue.43- Fails on every run, or only after a real code change: this is a44 regression, not a flake. Use superpowers:systematic-debugging instead.45- Check `gh run list` for the workflow: has this exact test or job failed46 before on unrelated PRs? Count occurrences of the exact signature across47 recent scheduled runs, and state the rate as per-run, not per-call -- one48 occurrence in 100 runs is not proof of absence. A recurring signature49 across unrelated changes is strong evidence of an environmental cause, not50 a code bug.5152## Step 2: classify by evidence, not guess5354Before matching a row, trace the failing message or exception string to the55code that emits it, and note whether the assertion sits in the test body or56in fixture setup/teardown (e.g. `RemoteApplicationFixture.TestForKnownProblems`57is a health check, not the test's own logic) -- this decides which row58applies.5960| Category | Signature | Evidence to pull |61|---|---|---|62| **Infra / cluster** | Whole class of tests fails identically across every framework/OS variant in the same run; error is a connection, timeout, or account/lock error thrown before any agent code runs | `az monitor metrics list` on the AKS node and load balancer; `kubectl get pods -n unbounded-services -o wide` for restarts/uptime; does a bare retry pass (transient) or fail identically (persistent server state)? |63| **Timing / race** | Assertion is a count or an event that "usually" arrives, on a harvest or aggregator cycle; failure shows the right data, late or short by one cycle | The agent log, via **analyze-dotnet-agent-logs** -- confirm the exact interleaving (e.g. Seen vs Sent counts) before touching the test |64| **Tooling / dependency bug** | Crash signature is inside a third-party tool's native code, not in test or agent code; all tests report `Passed`, only the collector/runner process dies | The crash stack (module name, access-violation code); the tool's own changelog/issue tracker for a fix version already in flight |65| **External / upstream** | Error surfaces in CI infrastructure setup (runner boot, docker daemon, VM image), not in the app or the agent at all | Search the CI vendor's own issue tracker (e.g. `actions/runner-images`) for the exact error string before assuming it's local |66| **Product defect, exposed nondeterministically** | The traced assertion is a health check or invariant (fixture setup/teardown), or the emitting code shows a structural hole (missing try/finally, unguarded async race) | The suspect code path traced above; a repro run that proves the path executed (see Toolkit) |6768Never conclude "just flaky" without pulling at least one row's evidence. See69[[feedback_no_guessing]] -- a root cause claim needs a log line, a metric, or70a matching upstream issue behind it, not a hunch. An assertion that exists to71catch a product defect is never reclassified as timing/race and its72threshold is never widened -- trace and fix the defect instead.7374## Step 3: pick the remedy for the category7576| Category | Remedy | Do NOT |77|---|---|---|78| Infra, transient (network blip) | CI-level retry-once on the job, plus raise the client-side timeout that actually stalled; document as an accepted risk | Silently widen unrelated timeouts repo-wide |79| Infra, persistent server state (account lock, stuck pod) | Operational fix (restart the pod/service); decide explicitly whether to harden the exposure, and record that decision even if the answer is "no, too costly" | Write code to retry around infra state that a retry cannot clear |80| Timing / race in event-harvest assertions | A race-free wait keyed on the **exact asserted evidence** (e.g. an `AgentLogBase.WaitForMetricAggregateCallCount`-style helper polling the real aggregate) | Bump a harvest-cycle magic number again -- each prior bump on the same test is evidence the race is still there, only the window changed |81| Tooling bug with no correctness impact | Wait for the upstream version bump already in flight; record the specific version and re-check after it lands | Change unrelated project settings (e.g. PDB format) as a workaround before confirming the bump doesn't already fix it |82| External/upstream CI regression | Apply the vendor's own recommended workaround inside the repo's CI composite action/step; link the upstream issue in a comment; remove the workaround once fixed upstream | Invent a bespoke workaround when the vendor already published one |83| Product defect, exposed nondeterministically | Trace the code path, file a ticket, leave the check alone; hand off to superpowers:systematic-debugging with the gathered evidence as the starting hypothesis and stop triage | Reclassify as timing/race, widen the assertion or its timeout, or suppress it to keep CI green |8485## Step 4: record the decision8687Every one of the categories above ends in a decision someone will ask about88again ("why don't we just fix the LB exposure", "did coverlet ever get89bumped"). Capture, in a project memory or handoff note:90911. The failure signature and the run/job that showed it.922. The evidence that ruled out the other categories.933. The remedy chosen, and what was explicitly rejected and why.944. Anything left open (a version to watch for, a follow-up not started).9596## Toolkit quick reference9798Each row runs inside a subagent per the hard rule above; the main session99sees only the verdict it returns.100101- Both `gh run view --job <id> --log-failed` and `--log` truncate silently102 past ~889 lines / 168KB, cutting off mid agent-log dump. For the complete103 text: `gh api repos/<org>/<repo>/actions/jobs/<jobid>/logs`. Copy any log to104 a file before a step that clears it -- lost evidence cannot be re-created.105 The pristine agent log also survives as CI artifact106 `integration-test-results-<matrix>` (`all_solutions.yml` uploads107 `C:\IntegrationTestWorkingDirectory\**\*.log` unconditionally).108- `az monitor metrics list --resource <aks-or-lb-id> --metric <name>` --109 cluster/LB health at the failure timestamp (`MSYS_NO_PATHCONV=1` needed for110 the slash-prefixed resource ID in Git Bash).111- `kubectl get pods -n unbounded-services -o wide` -- pod restarts/uptime112 after `az aks get-credentials`.113- **analyze-dotnet-agent-logs** skill -- agent-side evidence (harvest114 timing, Seen/Sent counts, connect sequence) from the test's own log.115- **run-integration-tests** skill -- reproduce the failing test locally116 before trusting a fix. A null result only counts if the code path is117 proven to have executed (log entry/exit counts); when the agent itself is118 a suspect, rerun once with the agent detached to isolate the confound.119 Delegate the run itself, per that skill's own subagent guidance; take back120 the summary, not the console output.121122## Common mistakes123124- Treating "all variants failed the same way" as proof of a code bug -- it125 is usually the opposite: identical failure across unrelated variants126 points at a shared external dependency, not the code under test.127- This pattern covers whole-class failures across unrelated variants only --128 a single test failing once, or a fixture health-check assertion firing, is129 not covered by it and may be the product-defect row instead.130- Fixing the assertion's timeout/retry count instead of the mechanism that131 makes the wait racy. If a similar bump has already happened once on this132 test, that is the signal to stop bumping and find the mechanism.133- Closing the investigation without writing down why the infra issue was134 not hardened -- the same question comes back next time the flake recurs.135- Reading a CI log or agent log directly in the main session "just this136 once" -- delegate it every time; a single raw read is how a multi-step137 investigation quietly fills the context window.