ci: bound the apt installs and prefer the canonical Ubuntu archive - #117
Conversation
Three Linux apt steps had no step-level and no job-level timeout, so a mirror fault hung them until the six-hour run limit rather than failing fast. Two such incidents happened on 2026-08-19 in sibling repos: an apt step ran 3h11m in openadapt-flow, and another ran over an hour on main in openadapt-capture. The hosted runner resolves its Ubuntu mirror through /etc/apt/apt-mirrors.txt, which points at azure.archive.ubuntu.com. apt does eventually fall back to the canonical archive, but it spends the whole budget getting there. Apply the pattern already merged and measured in openadapt-flow #374 (10-minute timeout to 83s) and openadapt-capture #81 (19s) to every apt step in this repository: - .github/workflows/build.yml, job native-installers, "Install Linux build dependencies" - .github/workflows/ffmpeg-runtime.yml, job build, "Install Linux build tools" - .github/workflows/native-release.yml, job build-linux, "Install Linux bundle dependencies" Each step gains timeout-minutes: 10, a best-effort rewrite of the mirror list to archive.ubuntu.com, and three bounded apt-get update attempts with backoff that fail closed with an ::error:: annotation. set -e is restored before the install, so an install failure still fails the step. Step names, package lists, and install flags are byte-identical to the previous revision; native-release.yml's signing, notarization, staging, and upload steps are untouched. These are the only three apt steps in the repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured evidence on a live runner
Job Native installer (Linux x86_64), runner Step Install Linux build dependencies: From that job's log:
This is the same order of magnitude as the two reference fixes: A pull request cannot exercise this step — 🤖 Generated with Claude Code |
What
Bound every
aptstep in this repository and point apt at the canonical Ubuntu archive.build.ymlnative-installers(matrix legLinux x86_64,if: runner.os == 'Linux')ubuntu-22.04ffmpeg-runtime.ymlbuild(matrix legLinux x86_64,if: runner.os == 'Linux')ubuntu-22.04native-release.ymlbuild-linuxubuntu-22.04These are the only three apt steps in the repository.
grep -rn "apt-get\|apt install\|add-apt-repository\|apt-key\|aptitude\|dpkg\|snap install" .github/workflows/finds no others, so nothing beyond the three listed above needed a fix. The other eight workflows (codeql,dependency-review,native-freshness,notify-docs,release-health,release,secret-scan,test) touch no package manager on Linux.Why
An apt step with no step-level timeout and no job-level timeout hangs until the run limit when the mirror faults. Two such incidents happened on 2026-08-19 in sibling repositories: an apt step ran 3h11m in
openadapt-flow, and another ran over an hour onmaininopenadapt-capturebefore it was cancelled.The hosted runner resolves its Ubuntu mirror through
/etc/apt/apt-mirrors.txt, which points atazure.archive.ubuntu.com. apt does fall back to the canonical archive on its own, but it spends the whole budget getting there.A job-level timeout would not be enough on its own: one hung step then consumes the entire job budget, and in
build.ymlandffmpeg-runtime.ymlthe job is a four-platform matrix whose other legs have nothing to do with apt.How
Each of the three steps keeps its name, its
if:guard, itsshell:key, and its package list, and gains:timeout-minutes: 10.sedrewrite of/etc/apt/apt-mirrors.txttoarchive.ubuntu.com. An absent or already-canonical file changes nothing (2>/dev/null || true).apt-get updateattempts with 10s/20s backoff, each failure raising a::warning::. After three failures the step fails closed with an::error::and never reaches the install.set -erestored before the install, so an install failure still fails the step.This is the pattern already merged and measured in
openadapt-flow#374 (a 10-minute timeout became 83s) andopenadapt-capture#81 (19s).Timeout sizing
All three are ordinary package installs, not toolchain downloads. The largest is
libwebkit2gtk-4.1-devplus the Tauri bundling dependencies in workflows 1 and 3;build-essential nasm pkg-config zlib1g-devin workflow 2 is mostly already present on the hosted image. A healthy mirror serves any of them in well under a minute, so 10 minutes is generous for all three and no step needed a larger budget.apt-get updateitself is separately bounded by the three-attempt loop.Release-workflow safety
native-release.ymlpublishes releases, so the change there is deliberately minimal. The diff for that file is 25 lines, all inside the one apt step. Untouched: thevalidategate,build-macossigning and notarization,build-windowssigning, theRefuse unverified Linux package-signing configurationpreflight, the sidecar build,npm run tauri build, the DEB/AppImage install-launch-uninstall smoke test,native_release.py stage, everyupload-artifact,attest,publish-draft,point-engine-release, andmirror-installers-to-engine-release.The Linux bundle contents depend on the installed packages, so the package list is byte-identical to the previous revision. Verified mechanically for all three files by diffing the
apt-get installblock againstorigin/main:Verification
actionlint— clean on the three changed files and on the whole workflow directory.shellcheckis on PATH, so actionlint linted eachrun:body as bash too.YAML parse —
python3 -c "import yaml;yaml.safe_load(open(F))"succeeds for all three files.shellcheck -s bash— clean on each of the threerun:bodies extracted from the parsed YAML.Behaviour — the
run:body was executed underbash -e(GitHub's default Linuxrun:shell) withsudo,apt-get, andsedstubbed. Thesedstub always exits non-zero, simulating an absent/etc/apt/apt-mirrors.txt:apt-get updatefailuresRow 2 shows the retry recovering. Row 3 shows it failing closed with
::error::apt-get update failed three times; the Ubuntu mirror is unreachable. Row 4 showsset -erestored, so a genuine install failure is not swallowed. Every row shows the failingsednot aborting the step.Real runner coverage. None of the three steps runs on a
pull_requestevent, so the pull-request checks alone do not exercise them:classify_build_scopereturnsBuildScope(True, False, "representative pull-request sidecar")for every pull request, sonative-installersis skipped here by design. (FULL_ARTIFACT_FILESonly widens the scope on apushtomain.)ffmpeg-runtime.ymlisworkflow_dispatchonly.native-release.ymltriggers only on adesktop-v*tag push.To get real evidence rather than an argument, step 1 was exercised on a live
ubuntu-22.04runner through this repository's own supported release-candidate path: aworkflow_dispatchofbuild.ymlon this branch, at the exact head, which setsrun_native=trueand runs the full four-platform matrix.build.ymlpublishes nothing — it uploads CI artifacts with 14-day retention underpermissions: contents: read. The run is linked in a comment below.Steps 2 and 3 stay unexercised on a pull request. Their
run:bodies are character-for-character identical to step 1 apart from the package list, and all three were verified statically and behaviourally as described above.No check was weakened or removed. No assertion or test was changed.
🤖 Generated with Claude Code