Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6541d2a
checkout python_sdk @v1.22.3
wvandeun Aug 19, 2026
1958a2a
release notes
wvandeun Aug 19, 2026
122fa24
version bump to v1.10.9
wvandeun Aug 19, 2026
bb13582
Merge pull request #10327 from opsmill/prep-release-1.10.9
wvandeun Aug 19, 2026
85201d8
chore: update docker-compose
opsmill-bot Aug 19, 2026
1e79bc2
checkout python_sdk @v1.23.0
wvandeun Aug 19, 2026
992fde3
Merge remote-tracking branch 'origin/stable' into prep-release-1.11.0
wvandeun Aug 19, 2026
c6f1fa1
Merge pull request #10335 from opsmill/prep-release-1.11.0
wvandeun Aug 19, 2026
79760e0
fix version in docker compose file
wvandeun Aug 19, 2026
9868ac6
test: stop the cache and message-bus fixtures from leaking their conf…
Aug 19, 2026
9ce0686
test: do not retry a failed Prefect task manager setup once per test …
Aug 19, 2026
e9b4b6d
fix(graphql): reject user-supplied branched_from on branch creation (…
ajtmccarty Aug 19, 2026
39fa696
perf(testcontainers): replace rabbitmq-diagnostics healthcheck with r…
Aug 19, 2026
b15b659
test: stop the webhook traceback fixture from reconfiguring logging p…
Aug 19, 2026
b0d02d8
test(webhook): guard that the traceback suppression fixture restores …
Aug 19, 2026
f3c7a85
test(log): cover that startup installs the traceback filter on the ru…
Aug 19, 2026
4dff0fe
test(log): cut the rejected alternative from the startup test docstring
Aug 19, 2026
928f911
test(log): move the logging-state guard into the unit suite
Aug 19, 2026
7abb3fe
test(webhook): identify the render test's own flow run instead of cou…
Aug 19, 2026
20fad94
fix(ci): retry cosign transparency-log writes and upload SBOMs first
petercrocker Aug 20, 2026
7c1c161
chore(deps): bump the uv group across 2 directories with 5 updates (#…
dependabot[bot] Aug 20, 2026
0daac5e
Merge pull request #10328 from opsmill/stable
ogenstad Aug 20, 2026
738693e
feat(agents): add analyzing-ci-flakiness skill
Aug 8, 2026
9769864
fix(agents): satisfy repo ruff/ty config in flakiness collector
Aug 8, 2026
540b042
feat(agents): harden flakiness collector from second-window dogfooding
Aug 19, 2026
afb26e5
fix(agents): stop re-downloading expired CI job logs every collection
Aug 19, 2026
ec9e367
fix(agents): match both renderings of the sqlite locked signature
Aug 19, 2026
02c54e2
feat(agents): add per-bucket incident counts to the flakiness report
Aug 19, 2026
cd97220
fix(agents): treat an empty successful log response as retryable
Aug 19, 2026
ee3c625
chore(agents): pre-approve the flakiness collector via allowed-tools
Aug 20, 2026
b27993d
Merge pull request #10344 from opsmill/stable
infrahub-github-bot-app[bot] Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .agents/rules/testing-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ Skip tests that only exercise library behavior: plain `Enum` value/round-trip ch

If the logic needs only in-memory inputs (a `SchemaBranch`, a dataclass, a pure function), write a unit test without DB fixtures — don't default to a component test because a neighbor uses one. Use the database or containers only when behavior genuinely depends on them.

## Don't leak process-global state

Every test in an xdist worker shares one interpreter. Change `logging` levels/handlers/filters, `structlog` config, module-level registries/singletons, `sys.path`/`sys.modules` or env vars only through a save/restore fixture (change it, `yield`, restore it), or `monkeypatch` where it applies. Never call an application startup routine such as `infrahub.log.configure_logging` from a test — it owns the whole process and undoes nothing, so it reconfigures every later test in the worker. Install only the piece under test and remove it after the `yield`. See `dev/guidelines/backend/testing.md` §"Leave process-global state as you found it".

## Test file placement

Test files mirror source structure: `infrahub/core/node.py` → `tests/unit/core/test_node.py`
Expand Down
128 changes: 128 additions & 0 deletions .agents/skills/analyzing-ci-flakiness/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
name: analyzing-ci-flakiness
description: >-
Analyzes recent CI failures on pull requests to identify flaky tests, using retry outcomes
(failed attempt → green re-run) and cross-PR recurrence as evidence, and maintains a local
longitudinal ledger so flakiness can be tracked over time. TRIGGER when: the user wants to find
flaky tests, correlate recent CI failures, check which tests fail across PRs or recover on
retry, or refresh the flakiness trend report. DO NOT TRIGGER when: babysitting a single PR's CI
until green → monitoring-pull-requests; diagnosing or fixing one specific failing test → the
bug-analysis skills.
argument-hint: "Optional base-branch glob(s) and window, e.g. `release-1.11 14` (default: all bases, last 7 days)"
allowed-tools:
- Bash(python3 .agents/skills/analyzing-ci-flakiness/scripts/collect.py:*)
compatibility: Requires the gh CLI authenticated against the repo. Python 3 (stdlib only). Writes a cache under ~/ci-cache.
metadata:
version: 0.1.0
author: OpsMill
---

# CI Flakiness Analyzer

## Introduction

A test is *flaky* when its failure does not reproduce on the same code: the run was retried and
went green, or the same test fails on unrelated PRs. This skill mines both signals from GitHub
Actions history, downloads the failed job logs once into a local cache, and appends every
observation to a ledger (`~/ci-cache/<owner>-<name>/ledger.jsonl`) so repeated invocations —
weekly, or ad hoc — accumulate trend data instead of starting from scratch.

The mechanical part (fetching, caching, test-name extraction, known-signature classification) is
done by the bundled script. Your job is the judgment part: separating flakes from real
regressions, spotting new systemic signatures, and writing the report.

## Step 1 — Parse arguments

- Base-branch filter: any arguments that look like branch names or globs (`release-1.11`,
`release-*`, `stable`). Default: no filter (all PR bases), which is usually what "how flaky is
CI" means. Filter when the user names a branch.
- Window: a bare integer is a number of days (default 7). An ISO date means "since that date".

## Step 2 — Collect

Run the bundled collector (repo-root relative):

```bash
python3 .agents/skills/analyzing-ci-flakiness/scripts/collect.py \
[--base <glob> ...] [--days N | --since YYYY-MM-DD] [--repo owner/name]
```

It prints a JSON report to stdout and writes everything under
`~/ci-cache/<owner>-<name>/windows/<since>_<until>/`:

- `runs.jsonl` — every `pull_request` workflow run created in the window
- `failed_jobs_with_tests.json` — failed jobs of the interesting run-attempts, with extracted
failing tests, systemic-bucket tags, and a `recovered_same_run` flag
- `report-data.json` — headline numbers, ranked per-test table, per-bucket incident counts
(`bucket_incidents`: distinct jobs/runs/PRs per systemic bucket), and the ledger's weekly
history
- `joblogs/<job_id>.log` — raw logs (ANSI intact; strip with `sed 's/\x1b\[[0-9;]*m//g'`)

Notes the script already accounts for — don't re-derive them:

- The runs API's `pull_requests` field is empty for many runs; the script joins runs to PRs
through every PR head commit SHA as well. Don't trust the field alone.
- "Interesting attempts" = every earlier attempt of a retried run (that's what the retry fixed)
plus final attempts that failed. Runs cancelled on attempt 1 are concurrency noise and skipped.
- Logs already on disk are never re-downloaded; the ledger is deduplicated by (job, test). Old
logs expire on GitHub's side (~90 days) — an empty `joblogs/*.log` means expired, not passing.

## Step 3 — Investigate what the script could not name

For failed jobs with an empty `tests` list and no bucket tag, read the log yourself (grep for
`##[error]`, `FAILED`, `Error:`, `Timeout`). Two outcomes:

- It matches a *new* systemic signature (infra failure that cascades over many tests). Add a
regex for it to `BUCKETS` in `collect.py` and to the table below, so future runs classify it.
- It's a genuine test failure the extraction regexes missed — note the test manually and
consider extending `extract_tests`.

### Known systemic signatures (as of 2026-08 — keep in sync with `BUCKETS` in collect.py)

| Bucket | Signature | Meaning |
|---|---|---|
| `stack-readiness` | `ServerNotResponsiveError … /api/schema/load` | Seeded testcontainers stack not ready; the whole pytest-playwright shard errors. One incident, not N flaky tests. |
| `vitest-mock-corruption` | `TypeError: vi.mocked(...).mockX is not a function` | vitest browser-mode module-mocking race; hits a different test file each time. |
| `prefect-setup-triggers-timeout` | `Setup triggers` task `ReadTimeout` | Prefect hang at session setup; downstream tests hit their own timeouts. |
| `neo4j-deadlock` | `Neo.TransientError.Transaction.DeadlockDetected` | Concurrent-write deadlock, usually integration suites under xdist. |
| `compose-boot-failure` | `docker compose … up --wait` non-zero exit | Stack never booted; job-level infra failure. |
| `sqlite-locked` | `(sqlite3.OperationalError) database is locked` (also matches the raw `sqlite3.OperationalError:` form) | Prefect's sqlite under contention. |
| `runner-oom` | `Process completed with exit code 137` | Runner OOM/SIGKILL; the mass test failures in the same job are casualties, not flakes. |
| `docker-network-pool-exhausted` | `all predefined address pools have been fully subnetted` | Leaked compose networks exhausted the docker address pools on a self-hosted runner. |
| `actions-download-429` | `Failed to download action … 429` | GitHub rate-limited its own action download; pure platform flake. |
| `pytest-green-exit-1` | green pytest summary directly followed by exit 1 | Session-teardown/plugin abort after all tests passed (e.g. testcontainers result reporting). |

## Step 4 — Judge: flake vs regression

For each test in the ranked table, classify:

- **Flaky (strong)** — fails on ≥2 unrelated PRs, or `recovered_on_retry > 0`. The more distinct
PRs, the stronger.
- **Flaky (weak)** — single occurrence with an infra-flavored error (locator timeout, transient
branch not found) and the PR later went green. List, but rank low.
- **Suspect regression, not a flake** — the same test fails on *every* attempt of the same
commit and the PR is still red, or the failures started only after a specific merge. Say so
explicitly; do not bury it in the flake list. Cross-check: does the test fail on any PR that
does not contain the suspect change?
- **Systemic bucket** — tests whose only failures carry a bucket tag are casualties, not causes.
Report the bucket (with the incident count from `bucket_incidents` in `report-data.json`), not
the individual tests.

Different tests failing on successive attempts of the same run = two independent flakes, not a
regression.

## Step 5 — Report

Write `ANALYSIS.md` into the window directory, then give the user a summary. Lead with the
ranked flake candidates. Include:

1. Headline numbers: PRs in scope, runs matched, retried runs, retried-and-recovered runs
(pure-flake evidence), hard failures.
2. Ranked flake candidates — test id, distinct PRs/runs, recovered-on-retry count, one-line
error cause. Group systemic buckets as single entries.
3. Suspected real regressions, clearly separated.
4. Trend — from `weekly_history` in `report-data.json`: which offenders are new this window,
which recur week over week, which disappeared (likely fixed). This section is the reason the
ledger exists; don't skip it once ≥2 windows of data exist.

Do not propose fixes unless asked; the deliverable is the evidence-ranked candidate list.
Loading
Loading