- name
- flaky-test-catcher
- description
- Detects broken and flaky acceptance tests from recent CI failures on main and opens structured GitHub issues for automated remediation. Follow this skill strictly when analyzing CI failures.
# Flaky Test Catcher — Analysis Protocol
## 1. Overview / Purpose
This skill defines the end-to-end protocol for detecting broken and flaky acceptance tests by analyzing recent CI failures on `main`, then opening structured GitHub issues so that automated remediation workflows can address the root causes.
You **must** follow this protocol strictly. Do not improvise or skip steps.
## 2. Inputs from pre-activation context
The workflow pre-activation step has already computed all run-level data. Do **not** re-query GitHub for run lists or issue counts. Use the values injected into your prompt:
| Variable | Meaning |
|---|---|
| `failed_run_ids` | JSON array of run IDs of failed `test.yml` runs on `main` in the last 3 days |
| `total_run_count` | Count of completed runs with meaningful conclusions (success, failure, timed\_out, neutral, action\_required) — cancelled runs excluded |
| `open_issues` | Current count of open `flaky-test` issues |
| `issue_slots_available` | How many new issues you may create (max 3) |
Parse `failed_run_ids` as a JSON array immediately. Example: `["12345678","87654321"]`.
## 3. Fetching job logs
For each run ID in `failed_run_ids`:
### 3.1 List jobs for the run
```
gh api /repos/{owner}/{repo}/actions/runs/{run_id}/jobs?per_page=100
```
Replace `{owner}` and `{repo}` with the values from the repository's GitHub remote (visible in `git remote get-url origin`).
### 3.2 Filter to relevant failing jobs
From the returned `jobs` array, keep only jobs where **both** conditions hold:
- `conclusion == "failure"`
- The job name contains `Matrix Acceptance Test` (this is the job group that runs acceptance tests)
Ignore infrastructure jobs (e.g. lint, build, generate) — they do not produce `--- FAIL:` lines.
### 3.3 Fetch the log for each failing job
The GitHub API log endpoint returns a ZIP archive. Use the `gh` CLI to stream plain-text logs for a job:
```bash
gh run view --job {job_id} --log | grep '^--- FAIL:'
```
**Log size warning**: Job logs can be very large (10 MB+). Do **not** load the full log into context. Instead:
- Stream the log output through `grep` so only matching lines are retained.
- Scan only for lines matching the `--- FAIL:` pattern (see §4).
- Use `grep -B3 -A3` or similar when you also need surrounding context for the "Sample Failure Output" issue section.
- Capture a small surrounding context (3–5 lines before/after each `--- FAIL:` line) for the "Sample Failure Output" issue section.
### 3.4 Pagination
If a job list response has 100 items and there may be more, check the `Link` header for a `next` page URL and repeat the request. In practice, a single run rarely has more than 100 jobs.
## 4. `--- FAIL:` extraction pattern
Scan each log for lines that match this exact pattern:
```
^--- FAIL: TestName (timing)
```
Examples of matching lines:
```
--- FAIL: TestAccResourceAgentConfiguration_alternateEnvironment (12.34s)
--- FAIL: TestAccSomeResource_basic (0.45s)
```
**Rules:**
- The line must start with `--- FAIL:` (three hyphens, a space, `FAIL:`, a space).
- The test name follows Go test naming: `TestAcc...` with optional underscore-separated sub-name.
- Extract **only the test function name** — strip the timing suffix `(12.34s)`.
- **Ignore** bare `FAIL` lines without the `---` prefix; those are package-level failure markers, not individual test failures.
Collect all extracted test names across all runs and all jobs. A test may appear multiple times (once per run where it failed) — track counts.
**Same-run deduplication**: If the same test name appears in multiple failing jobs within a single run (e.g. multiple shards both failing the same test), count it **only once** for that run. Deduplication is by run ID, not job ID — use a set per run when accumulating test names.
## 5. Fail-rate formula and thresholds
For each unique test name:
```
fail_rate = fail_count / total_run_count
```
Where:
- `fail_count` = number of distinct run IDs in which this test appeared as `--- FAIL:`
- `total_run_count` = the value from pre-activation context (already excludes cancelled runs)
| Classification | Condition | Action |
|---|---|---|
| **Broken** | `fail_rate == 1.0` (fails in 100% of runs) | Create issue |
| **Flaky** | `fail_rate >= 0.20` and `< 1.0` | Create issue |
| **Noise** | `fail_rate < 0.20` | Ignore — do not create issues |
## 6. Base-test-name grouping rule
Extract the **base test name** from each test function name using this rule:
> Take the substring from the beginning up to (but not including) the first underscore `_`.
Pattern: `TestAcc[^_]+`
Examples:
| Full test name | Base test name |
|---|---|
| `TestAccResourceAgentConfiguration_alternateEnvironment` | `TestAccResourceAgentConfiguration` |
| `TestAccResourceAgentConfiguration_minimal` | `TestAccResourceAgentConfiguration` |
| `TestAccResourceAgentConfiguration` | `TestAccResourceAgentConfiguration` |
| `TestAccSomeResource_basic` | `TestAccSomeResource` |
**One issue per base test name.** All scenario variants (subtests/suffixes) belonging to the same base name are consolidated into a single issue. List each specific variant inside the issue body.
**Fallback for non-`TestAcc` tests**: All acceptance tests in this project follow the `TestAcc` prefix convention. If a non-`TestAcc` test name appears in the logs, treat everything up to the first `_` (or the full name if no `_`) as the base name.
## 7. Commit analysis steps
For each base test name that will receive an issue, investigate whether any recent commit may already address the failure:
### 7.1 Find the oldest failing run timestamp
From the `failed_run_ids` list, identify the oldest run's `created_at` timestamp. You can get metadata for a single run:
```
gh api /repos/{owner}/{repo}/actions/runs/{run_id}
```
Take the minimum `created_at` across all failed runs.
### 7.2 Fetch commits on `main` since that timestamp
```
gh api "/repos/{owner}/{repo}/commits?sha=main&since={timestamp}&per_page=50"
```
Replace `{timestamp}` with the ISO 8601 value from step 7.1 (e.g. `2024-01-15T12:00:00Z`).
### 7.3 For each commit, check relevance
For each commit returned:
**a. Commit message relevance** — does it reference any of:
- The base test name (e.g. `TestAccResourceAgentConfiguration`)
- The resource name (derive from the test name, e.g. `agent_configuration` → `AgentConfiguration`)
- Keywords: `fix`, `flaky`, `test`, `revert`
**b. Changed file relevance** — fetch the full commit detail to get changed file paths:
```
gh api /repos/{owner}/{repo}/commits/{sha}
```
Check if any file in `files[].filename` matches patterns like:
- `*_test.go` files whose name contains a token from the resource name
- Files in the same Go package directory as the test
### 7.4 Include findings in issue body
- If a relevant commit is found:
```
⚠️ may already be addressed in `{short_sha}` — {one-line message summary}
```
- If no relevant commits found:
```
No recent commits appear to address this failure.
```
Frame the analysis as "has this been fixed yet?" — not as blame attribution.
**Do not suppress issue creation**: Even if a fix commit is found, always proceed with creating the issue and include the fix-detection note in the Commit Analysis section. The issue serves as the remediation trigger regardless.
## 8. Issue deduplication
Before creating an issue for a base test name, check whether one already exists:
```
gh api "/repos/{owner}/{repo}/issues?labels=flaky-test&state=open&per_page=100"
```
- Inspect each returned issue's `title`.
- The **full rendered title** for flaky-test issues is: `[flaky-test] {BaseTestName}` (the `[flaky-test] ` prefix is applied automatically by the `create-issue` safe output). When checking for duplicates, compare against this full title as it appears in GitHub.
- If an **exact title match** exists, skip creating a new issue for that base test name.
- Do **not** re-query or recalculate `issue_slots_available`; use only the value from pre-activation.
- If the response contains exactly 100 results, check for a `next` link in the `Link` response header and repeat the request for subsequent pages until all open issues are fetched.
## 9. Required issue body sections
**Issue title**: Pass only `{BaseTestName}` to the `create-issue` safe output — the `[flaky-test] ` prefix is added automatically.
Every issue you create must contain exactly these 5 sections in this order:
```markdown
## Broken Tests
List each test function name (including scenario suffix) that failed in 100% of runs:
- ❌ `TestAccResourceFoo_basic` — failed in 5/5 runs
## Flaky Tests
List each test function name that failed in ≥ 20% but < 100% of runs, with the observed rate:
- ⚠️ `TestAccResourceFoo_update` — failed in 3/5 runs (60%)
- ⚠️ `TestAccResourceFoo_import` — failed in 1/5 runs (20%)
## Commit Analysis
{Output from §7. Either a ⚠️ note about a possible fix commit, or the "No recent commits" message. Include commit SHA, message, and affected file paths when relevant.}
## Sample Failure Output
{Short excerpt (5–15 lines) of the actual log output surrounding a `--- FAIL:` line. Include any immediately preceding error messages for context.}
## Affected Stack Versions
{List the Elastic Stack versions / matrix dimension values (e.g. Elasticsearch version, Kibana version) from the failing job names or log metadata. If not determinable, write "Unknown — not present in log output".}
```
**Formatting rules:**
- Use `❌` for broken tests (100% fail rate).
- Use `⚠️` for flaky tests (20%–99% fail rate), and always include the fraction and percentage.
- Keep "Sample Failure Output" to the most informative excerpt; do not paste hundreds of lines.
**Cap enforcement**: Before creating each issue, verify that the number of issues created so far in this run has not reached `issue_slots_available`. Stop creating issues once the cap is reached, even if additional base test names remain.
## 10. Noop conditions
Call `noop` with a descriptive explanation (do not create any issues) when **any** of these conditions holds:
1. **All failures already have open issues** — every qualifying base test name matched an existing open `flaky-test` issue during deduplication; nothing new to open.
2. **All failures are below the 20% threshold** — every observed `--- FAIL:` test has `fail_rate < 0.20`; there are no broken or flaky tests to report.
3. **No `--- FAIL:` patterns found** — none of the logs for the provided `failed_run_ids` contained a `--- FAIL:` line; the CI failures were likely infrastructure failures (network timeouts, setup errors, etc.) rather than test logic failures.
When calling `noop`, state which condition applied and include basic counts (e.g. "3 failures observed, all below 20% threshold").
## Summary of execution order
1. Parse `failed_run_ids` from pre-activation context.
2. For each run ID: list jobs → filter to failing `Matrix Acceptance Test` jobs → fetch and scan logs for `--- FAIL:` lines.
3. Aggregate per-test fail counts across all runs.
4. Apply fail-rate thresholds; discard noise (`< 20%`).
5. Group surviving tests by base test name.
6. Deduplicate against existing open `flaky-test` issues.
7. For each remaining base test name (up to `issue_slots_available`): run commit analysis, then create an issue with all 5 required sections.
8. If no issues were created, call `noop`.
Voir sur GitHub