Summary
A distro-mirror stall in the Prereqs — <distro> matrix job of .github/workflows/installer-tests.yaml runs until the job's timeout-minutes: 20 fires and then reads as cancelled, with no log line saying why. It happened twice on 2026-09-04 on two package managers. The step that runs the prereqs is:
- name: Install prerequisites in ${{ matrix.distro }}
run: |
docker run --rm -v "$PWD:/src:ro" -w /src "$DISTRO" \
bash scripts/tests/distro-prereqs.sh
What IS already bounded, so this is not "add a timeout" as first written. scripts/tests/distro-prereqs.sh bounds its own bootstrap through _pm_run (three attempts of timeout ${TB_PM_TIMEOUT:-60} <pm> …, then a ::error:: naming mirror connectivity — added for exactly this class after backend#2859), and its header says the real installer functions it then invokes use common.sh's bounded probes. The sibling Pull <distro> (bounded, retried) step and Install bats (bounded, retried) bound their commands the same way. So somewhere in this leg a command reached the 20-minute cap past those bounds, and the ticket's job is to find which. Two candidates, neither verified yet because the cancelled job's step log is not retrievable after the cap fires:
_pm_run falls through to an unbounded call when the container has no timeout binary — measured false (see comment below): rockylinux:9 ships /usr/bin/timeout via coreutils-single, so the bounded helpers did bound what they wrapped. The fall-through itself remains a fail-open worth closing, but it is not this leg's mechanism.
- A later installer function runs a package-manager or network command that is not routed through a bounded probe (docker-ce repo setup, conntrack, helm download). If so, the fix is at that call site, not in the workflow.
Measured
| leg |
evidence |
Prereqs — rockylinux:9 (run 33889848786, attempt 1, PR #983) |
step "Install prerequisites in rockylinux:9" ran 15:40:32 → 16:00:41, the 20-minute cap to the second; the other 24 legs of the run were green. Conclusion cancelled. Nothing in the PR's diff reaches the prereqs path. |
Installer tests / PATH persist (the leg behind #980) |
apt-get update could not reach the Ubuntu mirror; gh run rerun --failed passed with no code change. #980 blamed a merge for it and had to be corrected. |
Both cost 20 minutes of wall clock and a reviewer holding an approval on a red check that was never about the diff.
Proposed fix
- First, make the cap legible: a
timeout on the docker run itself, sized just under timeout-minutes (say 18 min), so the step FAILS with the script's own last stdout/stderr in the log instead of being cancelled by the job cap with nothing to read. That alone turns the next occurrence into evidence about which command stalled.
- Close the fail-open regardless of which candidate measures true:
_pm_run (distro-prereqs.sh) and _bounded (common.sh) both fall through to a bare "$@" when neither timeout nor gtimeout exists, so an absent guard reads as a satisfied one — a distro without coreutils timeout passes the "bounded call" check by never being bounded. The probe should refuse (or install coreutils first) when command -v timeout is empty, the same way the repo's other guards fail closed.
- Then, if the evidence names it, route the unbounded installer call site through the bounded probe.
A mirror stall should end as a failed step with a reason in its log, never a cancelled job whose only evidence is that its start and end timestamps match timeout-minutes.
Summary
A distro-mirror stall in the
Prereqs — <distro>matrix job of.github/workflows/installer-tests.yamlruns until the job'stimeout-minutes: 20fires and then reads ascancelled, with no log line saying why. It happened twice on 2026-09-04 on two package managers. The step that runs the prereqs is:What IS already bounded, so this is not "add a timeout" as first written.
scripts/tests/distro-prereqs.shbounds its own bootstrap through_pm_run(three attempts oftimeout ${TB_PM_TIMEOUT:-60} <pm> …, then a::error::naming mirror connectivity — added for exactly this class after backend#2859), and its header says the real installer functions it then invokes usecommon.sh's bounded probes. The siblingPull <distro> (bounded, retried)step andInstall bats (bounded, retried)bound their commands the same way. So somewhere in this leg a command reached the 20-minute cap past those bounds, and the ticket's job is to find which. Two candidates, neither verified yet because the cancelled job's step log is not retrievable after the cap fires:— measured false (see comment below):_pm_runfalls through to an unbounded call when the container has notimeoutbinaryrockylinux:9ships/usr/bin/timeoutviacoreutils-single, so the bounded helpers did bound what they wrapped. The fall-through itself remains a fail-open worth closing, but it is not this leg's mechanism.Measured
Prereqs — rockylinux:9(run 33889848786, attempt 1, PR #983)cancelled. Nothing in the PR's diff reaches the prereqs path.Installer tests / PATH persist(the leg behind #980)apt-get updatecould not reach the Ubuntu mirror;gh run rerun --failedpassed with no code change. #980 blamed a merge for it and had to be corrected.Both cost 20 minutes of wall clock and a reviewer holding an approval on a red check that was never about the diff.
Proposed fix
timeouton thedocker runitself, sized just undertimeout-minutes(say 18 min), so the step FAILS with the script's own last stdout/stderr in the log instead of being cancelled by the job cap with nothing to read. That alone turns the next occurrence into evidence about which command stalled._pm_run(distro-prereqs.sh) and_bounded(common.sh) both fall through to a bare"$@"when neithertimeoutnorgtimeoutexists, so an absent guard reads as a satisfied one — a distro without coreutilstimeoutpasses the "bounded call" check by never being bounded. The probe should refuse (or install coreutils first) whencommand -v timeoutis empty, the same way the repo's other guards fail closed.A mirror stall should end as a failed step with a reason in its log, never a
cancelledjob whose only evidence is that its start and end timestamps matchtimeout-minutes.