Skip to content

Commit 6b84b1b

Browse files
committed
ci: guard against the self-hosted bun lagging the workflows' pinned version
The self-hosted lanes run with the bun on the runner's PATH snapshot while every hosted workflow installs a PINNED bun via setup-bun. The two share no source of truth, so the box silently fell six patches behind (1.3.8 vs the pinned 1.3.14). That drift was not cosmetic. The stale bun CRASHED and HUNG the lanes: 'Illegal instruction (core dumped) bun test' on one run, 20-30 minute job-cap stalls on others, and a 464ms test eating 60s on another -- 6+ red lanes in one day, each costing a re-run to learn nothing, because a dead runtime produces no failing test name. `bun upgrade` to the pinned version cleared it first try. A per-test --timeout does NOT mitigate this; a test-level cap cannot interrupt a wedged or dead runtime. New scripts/check-bun-version.sh compares `bun --version` against the highest `bun-version:` pinned in .github/workflows and fails loudly when local LAGS (being ahead is reported, not failed). Wired into the `gates` lane beside the existing pom-version parity guard, which exists for the same silent-drift class. Verified both directions: passes at 1.3.14, and exits 1 with the diagnostic when fed a fake 1.3.8 against the 1.3.14 pin.
1 parent e9d44ad commit 6b84b1b

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

‎scripts/check-bun-version.sh‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Guard: the bun on THIS machine must not lag the version the hosted workflows pin.
4+
#
5+
# Why this exists: the self-hosted runners execute `scripts/ci-local.sh` with the
6+
# bun on the runner's PATH snapshot (`~/.bun/bin/bun`), while every hosted workflow
7+
# installs a PINNED bun via `oven-sh/setup-bun` (`bun-version: 'X.Y.Z'`). Those two
8+
# paths share no source of truth, so the local bun can silently fall behind for
9+
# months — and on 2026-08-05 it had: the box sat on 1.3.8 while the workflows
10+
# pinned 1.3.14.
11+
#
12+
# That drift was NOT cosmetic. The stale bun CRASHED and HUNG on the runner:
13+
# `Illegal instruction (core dumped) bun test` on one run, a 20-30 minute job-cap
14+
# stall on others, and a 464ms test consuming 60s on another — 6+ red lanes in a
15+
# single day, each costing a full re-run to learn nothing, because a crashed
16+
# runtime produces no failing test name. Upgrading to the pinned version cleared
17+
# it first try. Note a per-test `--timeout` does NOT mitigate this: a test-level
18+
# cap cannot interrupt a wedged or dead runtime.
19+
#
20+
# This guard turns that silent drift into a loud, instant, toolchain-free failure.
21+
# Being AHEAD of the pin is fine (reported, not failed) — only lagging is the
22+
# dangerous direction.
23+
#
24+
# Usage: scripts/check-bun-version.sh # exit 1 + diagnostic when local < pinned
25+
#
26+
set -uo pipefail
27+
28+
cd "$(dirname "$0")/.."
29+
30+
if ! command -v bun >/dev/null 2>&1; then
31+
echo "check-bun-version: bun not installed — nothing to compare (skipping)."
32+
exit 0
33+
fi
34+
35+
local_ver="$(bun --version 2>/dev/null | tr -d '[:space:]')"
36+
if [ -z "$local_ver" ]; then
37+
echo "check-bun-version: could not read \`bun --version\` (skipping)." >&2
38+
exit 0
39+
fi
40+
41+
# Highest version pinned by any hosted workflow (setup-bun's `bun-version:`).
42+
pinned_ver="$(grep -rhoE "bun-version:[[:space:]]*['\"]?[0-9]+\.[0-9]+\.[0-9]+" .github/workflows/ 2>/dev/null \
43+
| grep -oE "[0-9]+\.[0-9]+\.[0-9]+" \
44+
| sort -V | tail -1)"
45+
46+
if [ -z "$pinned_ver" ]; then
47+
echo "check-bun-version: no \`bun-version:\` pin found in .github/workflows/ (skipping)."
48+
exit 0
49+
fi
50+
51+
# sort -V puts the older version first; if that is the local one, we lag.
52+
oldest="$(printf '%s\n%s\n' "$local_ver" "$pinned_ver" | sort -V | head -1)"
53+
54+
if [ "$local_ver" = "$pinned_ver" ]; then
55+
echo "check-bun-version: bun $local_ver matches the workflow pin."
56+
exit 0
57+
fi
58+
59+
if [ "$oldest" = "$local_ver" ]; then
60+
cat >&2 <<EOF
61+
62+
✖ bun on this machine LAGS the version the hosted workflows pin.
63+
64+
local (runner PATH): $local_ver
65+
pinned (setup-bun) : $pinned_ver
66+
67+
A stale bun has crashed and stalled the self-hosted lanes before —
68+
'Illegal instruction (core dumped)' and job-cap timeouts with no failing
69+
test name, which look like flakes and cost a re-run each. Fix with:
70+
71+
bun upgrade
72+
73+
then re-run. (Being AHEAD of the pin is fine; only lagging is guarded.)
74+
75+
EOF
76+
exit 1
77+
fi
78+
79+
echo "check-bun-version: bun $local_ver is ahead of the workflow pin ($pinned_ver) — OK."
80+
exit 0

‎scripts/ci-local.sh‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ gate_leak_scan() {
129129
# ── release hygiene: reactor-excluded pom version parity (drift guard) ─────────
130130
gate_pom_versions() { scripts/check-pom-versions.sh; }
131131

132+
# ── toolchain drift: local bun must not lag the hosted workflows' pin ─────────
133+
# The self-hosted lanes use the runner-PATH bun while hosted workflows pin one via
134+
# setup-bun; nothing keeps them in sync, and a stale bun has crashed/stalled these
135+
# lanes with no failing test name. See scripts/check-bun-version.sh.
136+
gate_bun_version() { scripts/check-bun-version.sh; }
137+
132138
# ── conformance.yml — fixture lint + workspace typecheck ──────────────────────
133139
gate_fixture_lint() {
134140
bun_install && ( cd server/typescript/packages/conformance && bun bin/conformance.ts lint ../../../../fixtures/conformance )
@@ -288,6 +294,7 @@ fi
288294
# that no-flags execution is byte-equivalent to the pre-refactor script.
289295
if want gates; then step "leak-scan (security)" gate_leak_scan; fi
290296
if want gates; then step "pom-version parity" gate_pom_versions; fi
297+
if want gates; then step "bun-version parity" gate_bun_version; fi
291298
if want gates; then step_if bun "fixture-lint" gate_fixture_lint; fi
292299
# The ts port is split into two lanes so CI can run them as separate jobs (see
293300
# .github/workflows/local-ci.yml): a FAST lane (build+typecheck, conformance,

0 commit comments

Comments
 (0)