Skip to content

CI: publish a weekly flaky test dashboard ("Soigneur") - #5201

Merged
AkihiroSuda merged 2 commits into
containerd:mainfrom
AkihiroSuda:flaky
Sep 17, 2026
Merged

AkihiroSuda merged 2 commits into
containerd:mainfrom
AkihiroSuda:flaky

Conversation

@AkihiroSuda

@AkihiroSuda AkihiroSuda commented Sep 10, 2026 •

Copy link
Copy Markdown
Member

Flaky test dashboard (Soigneur)

A test that fails once in a while, in one of the dozen environments the integration suite runs
in, is only visible to whoever looks at that one job. Nothing accumulates, so nothing gets fixed.

This adds a single, long-lived issue - #5202, named by number in the workflow - whose description
is rewritten every Monday with what the CI reported over the last week, plus a one-line digest as
a comment. Nothing ever opens an issue: there is exactly one, and it is the one named.
No database, no artifact, no server: the archived logs of the analyzed runs are the storage.

Real output, over two days of main, trimmed - the report also lists every job configuration
that failed, and links every occurrence to its job:

Test Occurrences Recovered on retry Commits affected Configurations Last seen
TestLoadStdinFromPipe 3 0 3/10 2 2026-09-09
TestAttachForAutoRemovedContainer 2 0 2/10 1 2026-09-10

gotestsum-reporter.sh hands its json file to flaky-annotate.sh, which writes one marker line
per test to the job log, including the tests that passed when --rerun-fails retried them - a
class that was already computed, then thrown away, yet the only one that proves flakiness.
Weekly, the collector lists the workflow runs of the branch and downloads the logs of each run in
a single request to read the markers back. Per run rather than per job: ~210 API requests a week
instead of ~880 through the annotations of the checks API, against a budget of 1000 per hour.

Everything reusable lives in mod/soigneur - Soigneur, a composite action; a soigneur is
the keeper who tends the animals, and this one keeps an eye on what Tigron runs. See
docs/testing/flaky.md and mod/soigneur/README.md.

Notes for review

  • Recovered on retry reads 0 until this merges: the markers are not in the archived logs
    yet. Everything else works on existing logs, because the collector also reads the
    === Failing tests === block the reporter has always printed - that produced the table above.
  • Only the integration suites report per-test data; the unit tests, Windows and FreeBSD do not.
  • Needs contents: read, actions: read, issues: write; never runs on a fork. Test names come
    from job logs, so implausible ones are dropped and the tables are escaped.
  • Tested by running the collector against this repository (a two-day window is 50 runs, 354 job
    executions, 18 MB, under 100 s) and cross-checking one job against the checks API.
    make lint-shell and a local zizmor v1.30.1 are clean; make lint-yaml was not run locally.
  • To see a report without touching any issue: Actions > flaky-test-dashboard > Run workflow,
    with publish unchecked.

🤖 Generated with Claude Code


Preview

Flaky test dashboard

Failures reported by the CI on main between 2026-09-08 and 2026-09-10 — 10 commits with CI results, 354 job executions.

Tests

Test Occurrences Recovered on retry Commits affected Configurations Last seen
TestLoadStdinFromPipe 3 0 3/10 2 2026-09-09
TestAttachForAutoRemovedContainer 2 0 2/10 1 2026-09-10
TestRunRmTime 2 0 2/10 2 2026-09-09
TestBuildContextWithOCILayout 1 0 1/10 1 2026-09-09
TestComposePsJSON 1 0 1/10 1 2026-09-10
TestComposeUpWithExternalNetwork 1 0 1/10 1 2026-09-09
TestContainerListWithFilter 3 cases 1 0 1/10 1 2026-09-10
TestIPFSAddrWithKubo 1 0 1/10 1 2026-09-09
TestLoadQuiet 1 0 1/10 1 2026-09-10
TestRunContainerWithStaticIP6 3 cases 1 0 1/10 1 2026-09-09
TestRunDomainname 2 cases 1 0 1/10 1 2026-09-09
TestSave 2 cases 1 0 1/10 1 2026-09-09
TestSaveContent 1 0 1/10 1 2026-09-09
TestSaveQuiet 1 0 1/10 1 2026-09-09
TestSyslogNetwork 4 cases 1 0 1/10 1 2026-09-10
TestLoadStdinFromPipe — 3 occurrences
When Job configuration Test Outcome
2026-09-09 in-host / rootless-port-slirp4netns linux TestLoadStdinFromPipe failed
2026-09-09 in-host / nerdctl.gomodjail < rootless linux TestLoadStdinFromPipe failed
2026-09-09 in-host / rootless-port-slirp4netns linux TestLoadStdinFromPipe failed
TestAttachForAutoRemovedContainer — 2 occurrences
When Job configuration Test Outcome
2026-09-10 in-host / docker < linux TestAttachForAutoRemovedContainer failed
2026-09-09 in-host / docker < linux TestAttachForAutoRemovedContainer failed
TestRunRmTime — 2 occurrences
When Job configuration Test Outcome
2026-09-09 in-host / rootless-port-slirp4netns linux TestRunRmTime failed
2026-09-09 in-host / rootless linux TestRunRmTime failed
TestBuildContextWithOCILayout — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootful linux TestBuildContextWithOCILayout failed
TestComposePsJSON — 1 occurrence
When Job configuration Test Outcome
2026-09-10 EL / almalinux-8 rootful TestComposePsJSON failed
TestComposeUpWithExternalNetwork — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootful linux (old ubuntu) (ctd: v1.7.34) TestComposeUpWithExternalNetwork failed
TestContainerListWithFilter — 1 occurrence
When Job configuration Test Outcome
2026-09-10 EL / almalinux-8 rootless TestContainerListWithFilter failed
TestIPFSAddrWithKubo — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootful linux TestIPFSAddrWithKubo failed
TestLoadQuiet — 1 occurrence
When Job configuration Test Outcome
2026-09-10 in-host / nerdctl.gomodjail < rootless linux TestLoadQuiet failed
TestRunContainerWithStaticIP6 — 1 occurrence
When Job configuration Test Outcome
2026-09-09 EL / almalinux-8 rootful TestRunContainerWithStaticIP6 failed
TestRunDomainname — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootful linux (old ubuntu) (ctd: v1.7.34) TestRunDomainname failed
TestSave — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootful linux TestSave failed
TestSaveContent — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootless linux (arm) TestSaveContent failed
TestSaveQuiet — 1 occurrence
When Job configuration Test Outcome
2026-09-09 in-host / rootless linux TestSaveQuiet failed
TestSyslogNetwork — 1 occurrence
When Job configuration Test Outcome
2026-09-10 EL / almalinux-8 rootful TestSyslogNetwork failed

Job configurations

Every job, test or not: a job that fails without any test failing is usually an environment, timeout, or infrastructure problem.

Job configuration Failure rate Failed Executions Recent failures
in-host / windows ████░░░░░░ 40% 4 10 1 2 3
in-host / rootful linux ███░░░░░░░ 30% 3 10 1 2 3
in-host / rootful linux (old ubuntu) (ctd: v1.7.34) ███░░░░░░░ 25% 2 8 1 2
EL / almalinux-8 rootful ██░░░░░░░░ 20% 2 10 1 2
in-host / docker < linux ██░░░░░░░░ 20% 2 10 1 2
in-host / nerdctl.gomodjail < rootless linux ██░░░░░░░░ 20% 2 10 1 2
in-host / rootless linux ██░░░░░░░░ 20% 2 10 1 2
in-host / rootless-port-slirp4netns linux ██░░░░░░░░ 20% 2 10 1 2
EL / almalinux-8 rootless █░░░░░░░░░ 10% 1 10 1
FreeBSD / FreeBSD █░░░░░░░░░ 10% 1 10 1
in-host / rootless linux (arm) █░░░░░░░░░ 10% 1 10 1
How this is collected

This dashboard is generated by
Soigneur,
which aggregates the GitHub Actions annotations that the test jobs attach to themselves.
No database, test artifact, or server is involved, so the window that can be reported on is
limited by the retention of the check runs (90 days, by default).

  • Occurrences: how many (commit × job configuration) executions reported the test as failing.
  • Recovered on retry: occurrences where the test did pass when it was retried
    (gotestsum --rerun-fails). Those are flaky beyond a doubt. The others are either flaky,
    or consistently broken.
  • Only the integration suites report per-test data: the unit tests, Windows, and
    FreeBSD do not run the reporter.

See https://github.com/containerd/nerdctl/blob/main/docs/testing/flaky.md to reproduce, fix, or quarantine a flaky test.

Generated by this run.

@AkihiroSuda AkihiroSuda added the area/ci e.g., CI failure label Sep 10, 2026
@AkihiroSuda AkihiroSuda added this to the v2.4.0 milestone Sep 10, 2026
@AkihiroSuda
AkihiroSuda marked this pull request as draft September 10, 2026 15:13
A test that fails once in a while, in one of the dozen environments an
integration suite runs in, is only visible to whoever happens to look at
that one job. Nothing accumulates, so nothing gets fixed.

Soigneur is a dashboard for those, with no infrastructure behind it: the
archived logs of the analyzed runs are the storage. A soigneur is the
keeper who tends the animals, and this one keeps an eye on what Tigron
runs: it has two halves, and one marker line per test is the contract
between them (see lib.sh):

- `flaky-annotate.sh` classifies the tests of a gotestsum json file at
  the end of a test job, and writes them to the job log as
  "flaky-test-dashboard: failing TestFoo" (failed, and never passed, even on
  retry) or "flaky-test-dashboard: flaky TestFoo" (failed, then passed when
  `--rerun-fails` retried it: flaky beyond a doubt). Both classes are
  also emitted as annotations, for the humans reading a pull request.
- `flaky-report.sh` lists the workflow runs of a branch and, for each of
  them, downloads the archived logs of all of its jobs in a single
  request and reads the markers back; `flaky-report.jq` renders the
  aggregate as markdown; `flaky-issue.sh` publishes it to the one issue
  whose number it was given, rewriting its description and posting the
  digest as a comment. It never opens an issue, which is the point:
  there is exactly one dashboard, and it is the one that was named.

Occurrences are deduplicated per (commit, job configuration, kind,
top-level test), so that a single bad job execution counts once, and
subtests do not inflate the ranking of their parent.

Collecting is deliberately per run, and not per job: a run's logs come
as one zip, so a week of a busy branch costs about 200 API requests and
50 MB, against the 1000 requests per hour and per repository that the
GITHUB_TOKEN of a workflow gets. Reading the annotations of each job
through the checks API instead would cost some 800 requests for the same
week. The markers are what gets collected, rather than those same
annotations, because the runner mangles a multi-line annotation into
orphaned fragments in the log. GitHub's job summaries, which do
aggregate all of this per run in the web UI, cannot be used at all: they
are not exposed by any API, and are rendered from signed attachments
only a browser session can mint.

The logs of the runs that predate the markers only hold the readable
block that flaky-annotate.sh prints at the end of a job, so that block
is read as a fallback, and a dashboard has history from the start rather
than after a full window.

`action.yml` wraps the collecting half as a composite action, so that
other projects can use it as-is: everything project-specific is an
input, including the caveats rendered in the report's methodology.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Use the mod/soigneur action, on both ends:

- gotestsum-reporter.sh hands its json file to flaky-annotate.sh, so
  that the tests that recovered on retry are reported too. That set was
  already computed, only to be excluded from the failing ones, and then
  thrown away - yet those are the tests that are flaky beyond a doubt,
  and the ones nobody could see, since a job that recovers is green.
  The step summary now lists them next to the failing ones.
- the flaky-test-dashboard workflow collects the week, every Monday on a
  quiet hour, and publishes it to containerd#5202, the issue it names. It only
  looks at the runs of the workflows that do run tests, and can also be
  dispatched manually, with a window, a branch, and a dry-run switch.

docs/testing/flaky.md covers what the dashboard reports, what it does
not (only the integration suites report per-test data), how to run the
collector locally, and how to reproduce, fix, or quarantine a flaky
test.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Comment thread mod/soigneur/README.md
@@ -0,0 +1,188 @@
# Soigneur

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @apostasie who named Tigron (#3889 (comment)), do you have a suggestion about the name?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great to see this!

Soigneur is not bad, especially if you plan in evolving this further into a system that could actively manage flakes.

Alternatively, if you want to stay with animals (aligning with tigron), I would probably go with oxpecker (a small bird that lives on larger animals and pick out their parasites / flakes).

remora (a fish) would be in the same vein.

If you want out of animals, I might go with heisen (from heisenbug).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see you @AkihiroSuda !
I miss being here...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, good to see you @apostasie

Alternatively, if you want to stay with animals (aligning with tigron),

I think I want something that monitors animals, so I still prefer "Soigneur" 🙂

"heisen" is also nice, but probably conflicts with something else

https://github.com/search?q=heisen

@AkihiroSuda
AkihiroSuda marked this pull request as ready for review September 10, 2026 18:04
@AkihiroSuda AkihiroSuda changed the title CI: publish a weekly flaky test dashboard CI: publish a weekly flaky test dashboard ("Soigneur") Sep 10, 2026
@AkihiroSuda
AkihiroSuda requested review from a team and ktock September 14, 2026 05:11
@AkihiroSuda
AkihiroSuda merged commit f4be8c4 into containerd:main Sep 17, 2026
67 of 73 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci e.g., CI failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants