From 4b9cec210da6d5aa77da3803f7ffa0a5530656f6 Mon Sep 17 00:00:00 2001 From: claude-hdb Date: Sat, 11 Jul 2026 19:37:48 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20source=20/etc/os-release=20in=20a=20subs?= =?UTF-8?q?hell=20=E2=80=94=20it=20clobbers=20$VERSION?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Debian, /etc/os-release defines VERSION="13 (trixie)". runner-install sourced it into the main shell for the Debian-family guard, overwriting the script's empty $VERSION: the latest-release resolution was skipped and the download URL became .../v13 (trixie)/... -> curl (3) malformed URL. A --version pin was clobbered the same way (guards run after arg parsing). Read ID/ID_LIKE via a subshell in both runner-install and bootstrap (same pattern, no collision there yet), and add a harness guard that fails on any future main-shell sourcing of os-release. Co-Authored-By: Claude Fable 5 --- commands/bootstrap.sh | 8 +++++--- commands/runner-install.sh | 8 +++++--- test/cli.sh | 6 ++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/commands/bootstrap.sh b/commands/bootstrap.sh index ac54a9a..cc0f4a9 100755 --- a/commands/bootstrap.sh +++ b/commands/bootstrap.sh @@ -58,11 +58,13 @@ fi # --- guards ------------------------------------------------------------------ [ "$(id -u)" -eq 0 ] || die "must run as root" if [ -r /etc/os-release ]; then + # Sourced in a subshell: os-release defines VERSION, NAME, ID, etc. — + # sourcing it in the main shell silently clobbers same-named script vars. # shellcheck source=/dev/null - . /etc/os-release - case "${ID:-} ${ID_LIKE:-}" in + OS_FAMILY="$(. /etc/os-release && printf '%s %s' "${ID:-}" "${ID_LIKE:-}")" + case "$OS_FAMILY" in *debian*) ;; - *) warn "not a Debian-family system (ID=${ID:-unknown}); proceeding anyway" ;; + *) warn "not a Debian-family system (${OS_FAMILY:-unknown}); proceeding anyway" ;; esac else warn "cannot read /etc/os-release; proceeding anyway" diff --git a/commands/runner-install.sh b/commands/runner-install.sh index 5941b2a..a2bf3d0 100755 --- a/commands/runner-install.sh +++ b/commands/runner-install.sh @@ -75,11 +75,13 @@ VERSION="${VERSION#v}" # --- guards ---------------------------------------------------------------- [ "$(id -u)" -eq 0 ] || die "must run as root" if [ -r /etc/os-release ]; then + # Sourced in a subshell: os-release defines VERSION (e.g. "13 (trixie)"), + # which would clobber this script's $VERSION. # shellcheck source=/dev/null - . /etc/os-release - case "${ID:-} ${ID_LIKE:-}" in + OS_FAMILY="$(. /etc/os-release && printf '%s %s' "${ID:-}" "${ID_LIKE:-}")" + case "$OS_FAMILY" in *debian*) ;; - *) warn "not a Debian-family system (ID=${ID:-unknown}); proceeding anyway" ;; + *) warn "not a Debian-family system (${OS_FAMILY:-unknown}); proceeding anyway" ;; esac else warn "cannot read /etc/os-release; proceeding anyway" diff --git a/test/cli.sh b/test/cli.sh index 69492b1..df8fbe8 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -68,6 +68,12 @@ else echo "skip: runner non-root refusal (running as root)" fi +# Regression: /etc/os-release defines VERSION (e.g. "13 (trixie)" on Debian); +# sourcing it in the main shell clobbers a script's $VERSION and splices the +# OS string into download URLs. It must only ever be sourced in a subshell. +check "no main-shell os-release sourcing" 1 "" \ + grep -rnE '^[[:space:]]*\.[[:space:]]+/etc/os-release' "$ROOT/commands" + echo "---" echo "$PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ] -- 2.45.2