Skip to content

CI retries cost 3x wall clock and rescue almost nothing #1798

Description

@sbryngelson

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 0Build 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions