ci: cache the Tauri apt .debs and fit a cold Windows Rust run in its cap (#8679)

## Summary

Fixes the two CI workflow defects that turned `main` red most often in
the 21–28 Oct 2026 audit:

- **Desktop Tauri:** the lane now caches its apt `.deb`s. It no longer
re-downloads 62 MB from a slow Ubuntu mirror on every run.
- **Windows Rust:** the lane drops a duplicate `cargo check` and gets a
60 min cap, so a cold run can save its cache.

It also updates #8613's `scripts/flake-check.test.mjs`, which breaks
once the Tauri package list moves into an env var. There are no product
code or test-behaviour changes.

This is one of seven PRs split out of the `flakey-tests` branch
(siblings below).

### Related issue

None found. #8613 touched both workflows after the audit, but not these
steps. No open PR changes how the Tauri packages are fetched, the
Windows timeout, or its Check (workspace) step:

- #8422, #8321 and #8373 only add packages to the list, and would now
add them to `TAURI_APT_PACKAGES`.
- #8414 adds two mesh-llm Tauri steps to the Windows job. If it lands,
they must fit the cold budget below.

### CI failures fixed

| Lane / test | Failing runs | Cause | Change |
|---|---|---|---|
| Desktop Tauri, "Install Tauri dependencies (Linux)"
(`_ci-desktop.yml`) | 62 jobs in 51 run attempts, 22–27 Oct. On main:
39821769576, 39822883395, 39827234118, 40333705511, 40342468627,
40348069929, 40353619362, 40368315739, 40373404267, 40374519539,
40556742929 | All 3 `ci-apt-retry.sh` attempts hit their 420 s limit
while `azure.archive.ubuntu.com` served the 62 MB at 20–65 KB/s. The
25.5 MB `libwebkit2gtk-4.1-0` alone takes a whole attempt, and each
attempt loses its partial download. | The package list moves to a
job-level `TAURI_APT_PACKAGES`. A new "Resolve Tauri apt archives" step
builds the cache key from `apt-get install --print-uris -o
Acquire::ForceHash=SHA256` (name, size and SHA256 per file).
`actions/cache/restore` uses that key. The install step copies only the
restored files whose SHA256 matches the signed index, because apt 2.8.3
reuses any same-size cached `.deb` without hashing it. A best-effort
rebuild keeps exactly the listed files. `actions/cache/save` runs only
on core-lane pushes after a successful rebuild; PR runs never save. |
| Windows Rust (`_ci-rust.yml`) | All on main: 39796569342 (job
119885795086), 39821769576 (119975358772), 39827234118 (119994286695),
39865872891 (120128351939), 40402556064 (121852634685) | Every test
passed. Cold steps take 41.3–43.5 min, so the 45 min cap cancelled the
`Swatinem/rust-cache` post-run save 1.6–3.7 min in. No cache was
written, and the next push was cold again. | Delete "Check (workspace)".
The clippy step before it covers the same `--workspace --all-targets`
set with `-D warnings`, which saves 2.0–2.8 min. Raise `timeout-minutes`
from 45 to 60, with the measured breakdown in a comment: 39–41 min of
steps, a ~4.5 min save and ~15 min of margin. |
| `scripts/flake-check.test.mjs` "repeat invocations mirror the lanes
they stand in for" | None in CI yet: it fails only with this change
applied | #8613 added this test after `flakey-tests` branched. It checks
that the advisory `flake-tauri` lane installs the same packages as the
Desktop Tauri core lane, by regex-matching the inline `apt-get install
-y --no-install-recommends \` list in `_ci-desktop.yml`. That list now
lives in `TAURI_APT_PACKAGES`, so the regex matched nothing and the test
failed with "Desktop Tauri apt packages". | Read `TAURI_APT_PACKAGES`
from `_ci-desktop.yml` and compare package names (12 on each side) with
`_ci-flake.yml`'s list. Renaming one package in `_ci-flake.yml` still
fails the test. Giving the `flake-tauri` lane the same read-only `.deb`
restore is a follow-up. |

Local evidence is from the original `flakey-tests` commit (9aa768a3b):

- The apt steps ran verbatim in Ubuntu 24.04 containers with apt 2.8.3,
with the cache restore and the save step's `if:` emulated.
- On a miss, the install fetches everything ("Need to get 254 MB"). On
an exact hit with the network down, it fetches nothing ("Need to get 0
B/254 MB").
- On a fallback with one `.deb` missing and one corrupted at the same
size, the hash check rejects the corrupt file, and apt re-fetches only
the corrupt and missing files.
- The key was identical in all four simulated jobs.
- The Windows numbers come from the jobs API for the five runs above.

On this branch, the split session ran the `scripts/` node tests:
`ci-selection`, `ci-required-gates`, `ci-apt-retry` and `flake-check` on
macOS gave 84 pass, 0 fail and 8 skipped (Linux-only). Before the
`flake-check.test.mjs` fix, that test failed 1 case. The same session
ran actionlint 1.7.12 with shellcheck, which was clean, and the
rust-cache contract scripts, which passed.

### Verification

This branch was pushed without further local re-verification, by the
user's choice: local test runs were overloading the machine and failing
for that reason. **Hosted CI is the check.** It was pushed with `git
push --no-verify` over SSH. The pre-push hook runs tests, and the HTTPS
token lacks the `workflow` scope.

Only hosted CI can show:

- The runner's real package list (about 62 MB) and the same key in all
three Tauri lanes.
- The `actions/cache` round trip. The first PR run misses, because
`main` has no key yet. After merge, the first push to `main` saves it.
- A cold Windows run saving its cache within the new cap. The next
toolchain bump or cache eviction will show it.

Remaining risk: after Ubuntu updates a large package, runs fetch it from
the mirror until a push to `main` saves a new key.

### Readiness

CI-only change. AGENTS.md's readiness steps (agent review, then human
confirmation) are still outstanding, so `buzz-review-completed` is not
set.

### Merge order

Independent of the other six. It merges cleanly onto `main`, and with
every sibling, in any order.

### Sibling PRs

Split from `flakey-tests` (one PR per area): #8676 (PostgreSQL lease +
lane), #8677 (relay E2E helper + Windows shell), #8678 (mobile), #8680
(desktop product races), #8681 (desktop E2E state gates), #8682 (desktop
E2E composer + row actions).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matt Toohey
2026-10-30 10:00:00 +11:00
committed by GitHub
parent 0052b2354e
commit b1f76ef4d2
3 changed files with 98 additions and 27 deletions

View File

@@ -69,7 +69,7 @@ jobs:
desktop-tauri:
name: Desktop Tauri (${{ matrix.lane }})
runs-on: ubuntu-latest
# A lane needs up to ~15m. ci-apt-retry.sh bounds the apt step at ~34m worst
# A lane needs up to ~15m. ci-apt-retry.sh bounds the apt steps at ~34m worst
# case (update ~8.5m + install ~25m); 55m fits both, so a degraded mirror
# fails with the wrapper's error instead of the job timeout.
timeout-minutes: 55
@@ -93,6 +93,22 @@ jobs:
run: just desktop-tauri-test-compiled-flags
permissions:
contents: read
env:
# Resolved and installed by the steps below; the .deb cache key hashes
# what apt resolves for this list.
TAURI_APT_PACKAGES: >-
build-essential
curl
file
libasound2-dev
libayatana-appindicator3-dev
libgtk-3-dev
librsvg2-dev
libssl-dev
libwebkit2gtk-4.1-dev
libxdo-dev
patchelf
wget
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
@@ -104,24 +120,70 @@ jobs:
workspaces: desktop/src-tauri
shared-key: ${{ matrix.cache }}
save-if: ${{ matrix.save-cache && github.event_name != 'pull_request' }}
# A degraded Ubuntu mirror serves .debs at ~50 KB/s, and the ~62 MB
# Tauri download (one 25.5 MB libwebkit2gtk .deb) then outlasts every
# ci-apt-retry.sh attempt. Cache the .debs in the Actions cache so the
# mirror only serves `apt-get update` and packages Ubuntu has updated
# since main last saved.
- name: Resolve Tauri apt archives
id: tauri-debs
run: |
set -euo pipefail
CI_APT_ATTEMPT_SECONDS=90 scripts/ci-apt-retry.sh sudo apt-get update
read -ra packages <<< "$TAURI_APT_PACKAGES"
# Name, size and SHA256 (from the signed index) of each .deb the
# install will fetch. The mirror URI is left out so the key doesn't
# depend on which mirror answered. Runs before the restore:
# --print-uris omits files that are already in
# /var/cache/apt/archives.
sudo apt-get install --print-uris -qq -y --no-install-recommends \
-o Acquire::ForceHash=SHA256 "${packages[@]}" |
awk '{print $2, $3, $4}' | sort > "$RUNNER_TEMP/tauri-debs.txt"
. /etc/os-release
prefix="tauri-debs-${ID}${VERSION_ID}-"
echo "prefix=$prefix" >> "$GITHUB_OUTPUT"
echo "key=$prefix$(sha256sum "$RUNNER_TEMP/tauri-debs.txt" | cut -c1-16)" >> "$GITHUB_OUTPUT"
- name: Restore Tauri apt archives
id: tauri-debs-cache
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ~/.cache/tauri-debs
key: ${{ steps.tauri-debs.outputs.key }}
restore-keys: ${{ steps.tauri-debs.outputs.prefix }}
- name: Install Tauri dependencies (Linux)
id: tauri-debs-install
env:
DEBIAN_FRONTEND: noninteractive
run: |
CI_APT_ATTEMPT_SECONDS=90 scripts/ci-apt-retry.sh sudo apt-get update
scripts/ci-apt-retry.sh sudo apt-get install -y --no-install-recommends \
build-essential \
curl \
file \
libasound2-dev \
libayatana-appindicator3-dev \
libgtk-3-dev \
librsvg2-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
libxdo-dev \
patchelf \
wget
set -euo pipefail
debs=~/.cache/tauri-debs
mkdir -p "$debs"
# apt reuses an archived .deb of the expected size without checking
# its hash, so seed only restored files whose SHA256 matches the
# signed index. apt downloads and verifies the rest. sha256sum exits
# non-zero when a file fails or none is restored; neither is fatal.
awk '{sub(/^SHA256:/, "", $3); print $3 " " $1}' "$RUNNER_TEMP/tauri-debs.txt" > "$RUNNER_TEMP/tauri-debs.sha256"
(cd "$debs" && { sha256sum --check --ignore-missing "$RUNNER_TEMP/tauri-debs.sha256" || true; }) |
sed -n 's/: OK$//p' | (cd "$debs" && xargs -r sudo cp -t /var/cache/apt/archives/)
read -ra packages <<< "$TAURI_APT_PACKAGES"
scripts/ci-apt-retry.sh sudo apt-get install -y --no-install-recommends "${packages[@]}"
# Keep exactly the resolved files, so a restore-keys fallback can't
# pile up superseded versions. The packages are installed by now, so
# a failure here only warns and skips the save.
if rm -rf "$debs" && mkdir -p "$debs" &&
awk '{print "/var/cache/apt/archives/" $1}' "$RUNNER_TEMP/tauri-debs.txt" | xargs -r cp -t "$debs"; then
echo "rebuilt=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Could not rebuild the Tauri apt archive cache; not saving it."
fi
# Like the other caches here, only pushes save; PR runs restore. All
# three lanes resolve the same key, so one lane saves it.
- name: Save Tauri apt archives
if: matrix.lane == 'core' && github.event_name == 'push' && steps.tauri-debs-cache.outputs.cache-hit != 'true' && steps.tauri-debs-install.outputs.rebuilt == 'true'
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ~/.cache/tauri-debs
key: ${{ steps.tauri-debs.outputs.key }}
- name: Desktop Tauri ${{ matrix.lane }}
run: ${{ matrix.run }}
env:

View File

@@ -111,9 +111,12 @@ jobs:
windows-rust:
name: Windows Rust (x86_64-pc-windows-msvc)
runs-on: windows-latest
# Windows runners are slow and this compiles the workspace + Tauri crate
# cold across four steps; budget generously.
timeout-minutes: 45
# With no rust-cache (after a toolchain bump or eviction), the steps take
# 39-41m on main (2026-10-22..26): clippy 17-18m, Tauri check ~6m, Tauri
# tests 10-11m, the rest ~5m. The cache save that follows takes ~4.5m and
# runs inside this cap. If the cap kills it, nothing is saved and the next
# push is cold again. 60m leaves ~15m over that; warm runs take 15-23m.
timeout-minutes: 60
if: inputs.lane == 'required' && (github.event_name == 'push' || inputs.rust || inputs.desktop_rust)
permissions:
contents: read
@@ -144,10 +147,10 @@ jobs:
for bin in buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz; do
touch "desktop/src-tauri/binaries/${bin}-${TARGET}.exe"
done
# Clippy type-checks everything `cargo check` would, so no separate
# workspace check step.
- name: Clippy (workspace)
run: cargo clippy --workspace --all-targets --target $env:TARGET -- -D warnings
- name: Check (workspace)
run: cargo check --workspace --all-targets --target $env:TARGET
- name: Test (buzz-dev-mcp)
# The Windows-only bash resolver lives in buzz-dev-mcp; its unit tests
# only gate if this crate is tested ON Windows.