Split out of #1797, which touched adjacent code but deliberately left this alone — it is a behavioural change to the test harness rather than CI plumbing.
The measurement
Over 540 first-attempt job failures (2026-08-18..31), every retry mechanism in the CI path:
| mechanism |
cases retried |
rescued by the retry |
bench.py per-case (max_attempts = 2) |
235 |
0 |
mfc.sh test --max-attempts 3 |
2,795 failed-test records |
every one shows Attempts: 3 |
build.sh / retry_build (2 attempts) |
210 Build failed after 2 attempts |
0 — Build succeeded on attempt 2 appears zero times in 552 logs |
retry_sbatch (3 attempts) |
1 job ever reached attempt 2 |
81 correctly refused as non-transient |
| GitHub whole-run re-run |
18 reaching a verdict |
11 (61%) |
Only the whole-run re-run helps, and only because hours pass and the node or queue situation changes in the meantime.
Why the in-process retries cannot help
handle_case in toolchain/mfc/test/test.py retries on any exception:
except Exception as exc:
if nAttempts < max_attempts:
continue
That includes golden-file tolerance mismatches — the single most common failure class in the window (88 of 540) — which are deterministic by construction. Re-running the same binary on the same input cannot produce a different comparison. The same applies to compile errors surfaced through the same path.
So the suite spends 3x the wall clock on the failures it is least able to fix, and the retry is also what makes a real failure take three times as long to surface.
Suggested direction
Retry only classes that are plausibly transient — execution failures, launcher/MPI faults, timeouts — and fail fast on deterministic ones (tolerance/golden mismatch, compile errors). handle_case already classifies the exception for its error_type field, so the information needed is present at the decision point; it just is not consulted before continue.
Worth pairing with a count of how often a retry does rescue a case, so the value is measurable rather than assumed.
Note
bench.py's 0-of-235 is the cleanest evidence, because there both the retry and its outcome are logged. The test-suite figure is one-sided by construction — only failures record Attempts: — but 2,795 records all at the maximum, with zero observed rescues anywhere else, makes a sizeable rescue rate implausible.
Split out of #1797, which touched adjacent code but deliberately left this alone — it is a behavioural change to the test harness rather than CI plumbing.
The measurement
Over 540 first-attempt job failures (2026-08-18..31), every retry mechanism in the CI path:
bench.pyper-case (max_attempts = 2)mfc.sh test --max-attempts 3Attempts: 3build.sh/retry_build(2 attempts)Build failed after 2 attemptsBuild succeeded on attempt 2appears zero times in 552 logsretry_sbatch(3 attempts)Only the whole-run re-run helps, and only because hours pass and the node or queue situation changes in the meantime.
Why the in-process retries cannot help
handle_caseintoolchain/mfc/test/test.pyretries on any exception:That includes golden-file tolerance mismatches — the single most common failure class in the window (88 of 540) — which are deterministic by construction. Re-running the same binary on the same input cannot produce a different comparison. The same applies to compile errors surfaced through the same path.
So the suite spends 3x the wall clock on the failures it is least able to fix, and the retry is also what makes a real failure take three times as long to surface.
Suggested direction
Retry only classes that are plausibly transient — execution failures, launcher/MPI faults, timeouts — and fail fast on deterministic ones (tolerance/golden mismatch, compile errors).
handle_casealready classifies the exception for itserror_typefield, so the information needed is present at the decision point; it just is not consulted beforecontinue.Worth pairing with a count of how often a retry does rescue a case, so the value is measurable rather than assumed.
Note
bench.py's 0-of-235 is the cleanest evidence, because there both the retry and its outcome are logged. The test-suite figure is one-sided by construction — only failures recordAttempts:— but 2,795 records all at the maximum, with zero observed rescues anywhere else, makes a sizeable rescue rate implausible.