unsloth/.github/scripts/clean-machine-assert.sh
danielhanchen f09a1e71a9 Tighten the clean-machine CI comments
Comments only, no assertion logic, pins or leg definitions touched.

Reflowed every rationale block to denser wording and removed the
duplication that had built up across repeated steps: the desktop
workflow repeated the fork-PR skip, the desktop-v* tag resolution and
the restore-runner note once per platform, and the installer workflow
repeated its path-filter rationale in both the pull_request and push
blocks. Those now point at the first copy.

Every WHY is kept: why the masked legs avoid install.sh --local, what
UNSLOTH_CI_SOURCE_OVERLAY is for, why `absent` tests "must not work"
rather than command -v, why the .venv_t5_* sidecars are in the macho
scan scope, why the signature check is main-executables-only, why each
nobuild allowlist entry is a pure-Python sdist, why the WSL job gates
and what the pipe truncation was, and why the virgin container's
overlay=false row is still pinned.

Proved comments-only three ways: both workflow revisions parsed with
yaml.safe_load_all and every leaf walked (only `run:` scalars differ);
every changed bash body and .sh compared byte-for-byte after
`bash --pretty-print -n`; every changed pwsh body and .ps1 compared as
a token stream with Comment and NewLine tokens dropped. A negative
control injecting one non-comment line into each layer makes all of
them fail.
2026-07-29 06:33:16 +00:00

261 lines
13 KiB
Bash
Executable file

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
#
# Assert the clean-machine contract after an install attempt.
#
# absent The toolchain really was absent for the whole run. Catches a leg that
# "passed" because masking silently failed, or because the installer
# quietly installed Xcode CLT behind our back.
# notools The trace recorded no compiler/git/brew invocation (trace mode).
# nobuild Wheels-only: no "Building wheel" from pip, no "Building <pkg>==<ver>"
# from uv. Needs UNSLOTH_VERBOSE=1, or run_install_cmd
# (install.sh:193-243) discards uv's output on success.
# macho Every Mach-O under $MACHO_ROOT is the host architecture, and every
# Mach-O MAIN EXECUTABLE is signed. Closes the Rosetta 2 gap, the one
# divergence masking cannot reproduce.
#
# Usage: bash .github/scripts/clean-machine-assert.sh absent notools nobuild macho
set -uo pipefail
LOG="${INSTALL_LOG:-logs/install.log}"
TRACE="${UNSLOTH_TOOL_TRACE:-}"
rc=0
fail() { echo "::error::$*"; rc=1; }
ok() { echo "[assert] OK $*"; }
for check in "$@"; do
case "$check" in
absent)
# NOT `command -v`: on a virgin Mac /usr/bin/{git,cc} EXIST as CLT stubs, so it
# succeeds and only RUNNING them fails. The invariant is: must not WORK.
if xcode-select -p >/dev/null 2>&1; then
fail "xcode-select -p still resolves to $(xcode-select -p 2>/dev/null); not a clean Mac"
else
ok "xcode-select -p fails (the gate a virgin Mac hits)"
fi
for tool in git cc clang cmake; do
command -v "$tool" >/dev/null 2>&1 || { ok "$tool not on PATH"; continue; }
if "$tool" --version >/dev/null 2>&1; then
# On Intel runners /usr/bin/git is not CLT-provided, so no masking can take
# it away. cc and clang do become stubs and the macOS consumer path needs
# no git, so report rather than fail.
case " ${UNSLOTH_CLEAN_ALLOW_WORKING:-} " in
*" $tool "*)
echo "[assert] NOTE $tool still works ($(command -v "$tool")); allowed on this runner"
continue
;;
esac
fail "toolchain still usable: '$tool --version' succeeded ($(command -v "$tool")); masking failed"
else
ok "$tool present but non-functional (CLT stub), as on a clean Mac"
fi
done
# brew is a plain binary with no stub, so absence from PATH is the right test.
if command -v brew >/dev/null 2>&1; then
fail "Homebrew still on PATH at $(command -v brew); masking failed"
else
ok "brew absent"
fi
;;
notools)
if [ -z "$TRACE" ] || [ ! -f "$TRACE" ]; then
fail "notools requested but no trace file (\$UNSLOTH_TOOL_TRACE=$TRACE)"
else
# git is legitimate under --local (unsloth-zoo comes from a git URL), so that
# leg allow-lists it via UNSLOTH_ALLOW_TOOLS.
allow="${UNSLOTH_ALLOW_TOOLS:-}"
hits=""
while IFS=$'\t' read -r tool rest; do
[ -n "$tool" ] || continue
case " $allow " in *" $tool "*) continue ;; esac
# `xcode-select -p` only ASKS whether a toolchain is selected, and the fix
# is that the installer carries on without one, so it is not USE.
# `--install`, which pops the CLT installer, stays a hit.
if [ "$tool" = "xcode-select" ]; then
case "$rest" in
-p|--print-path|-v|--version|"") continue ;;
esac
fi
hits="$hits $tool"
done < "$TRACE"
if [ -n "$hits" ]; then
fail "installer invoked toolchain:$(echo "$hits" | tr ' ' '\n' | sort -u | tr '\n' ' ')"
echo "---- tool trace ----"; sort -u "$TRACE" | head -50
else
ok "no compiler/git/brew invocation recorded"
fi
fi
;;
nobuild)
# "Built an sdist" is NOT "needed a compiler". Every name below was verified
# against its own sdist: setuptools.build_meta backend, no ext_modules, no
# .c/.cpp/.pyx/.rs file, so its PEP 517 build is a pure-Python copy step.
# openai-whisper, argbind, randomname -- no version ever ships a wheel
# antlr4-python3-runtime==4.9.3 -- pinned below the 4.13.2 wheel
# triton-kernels -- requirements/triton-kernels.txt pins a git URL under the
# triton repo's python/triton_kernels subdir: 75 Python files, a four-line
# pyproject.toml, no setup.py, kernels compiled at runtime. A direct URL the
# installer names itself, not something resolution chose, and only the Linux
# legs reach it (install_python_stack.py skips it on Windows and macOS).
# UNSLOTH_ALLOW_SDIST extends the allowlist.
#
# Lowercased and underscore-folded on both sides: a distribution name and the
# name uv prints can disagree on the separator (triton_kernels vs triton-kernels).
_allow="$(printf '%s' "openai-whisper argbind randomname antlr4-python3-runtime triton-kernels ${UNSLOTH_ALLOW_SDIST:-}" | tr 'A-Z_' 'a-z-')"
if [ ! -f "$LOG" ]; then
fail "nobuild requested but $LOG is missing"
else
# uv prints `Building <name>==<version>`, pip prints `Building wheel for
# <name>` (astral-sh/uv#11165), so match both; the `==` or ` @ ` requirement
# keeps this off the installer's own lowercase "building frontend..." text, and
# ANSI is stripped first so a coloured run (FORCE_COLOR) parses.
# `Building <name> @ file://...` is dropped: a local-path build is one the
# caller pointed at (--local, or the editable overlay), never one resolution
# chose. Index dependencies always print `<name>==<version>`, so a genuine
# PyPI sdist is still caught, including one named unsloth.
_esc=$(printf '\033')
_built="$(sed -E "s/${_esc}\[[0-9;]*[A-Za-z]//g" "$LOG" 2>/dev/null \
| grep -viE "building [a-z0-9._-]+ @ file://" \
| grep -oiE "building wheel for [a-z0-9._-]+|building [a-z0-9._-]+(==| @ )" \
| tr 'A-Z' 'a-z' \
| sed -E -e 's/^building wheel for //' -e 's/^building //' -e 's/(==| @ )$//' \
| tr '_' '-' \
| sort -u || true)"
_bad=""
for pkg in $_built; do
case " $_allow " in *" $pkg "*) continue ;; esac
_bad="$_bad $pkg"
done
if [ -n "$_bad" ]; then
fail "built from source:$_bad -- these must resolve to wheels on a clean machine"
else
[ -n "$_built" ] && say_built="$(echo "$_built" | tr '\n' ' ')" || say_built="none"
ok "no non-allowlisted source build (built: $say_built)"
fi
# Independent of package names: a compiler error means a toolchain was needed.
if grep -qiE "error: command '(cc|gcc|clang|cl)' failed|no such file or directory: 'cc'|clang: error|cargo: not found|error: linker \`cc\` not found" "$LOG"; then
fail "compiler invocation appears in the install log"
grep -iE "error: command '(cc|gcc|clang|cl)' failed|clang: error" "$LOG" | head -10
fi
fi
;;
macho)
# The one thing masking cannot reproduce: Rosetta 2 is preinstalled on hosted
# runners and absent from a factory-fresh Mac, so an x86_64-only payload runs
# green here and dies with "bad CPU type in executable" for the user. `lipo` is an
# xcrun shim and gone after masking, so read `file -b`, keyed off `uname -m`
# (macos-15-intel is x86_64).
#
# SCOPE: all of $MACHO_ROOT, .venv_t5_510/_530/_550 sidecars included. Those are
# payload, not scratch: setup.sh:579-581 creates them during a normal install and
# transformers_version.py:338-348 puts them on sys.path. Any exclusion must be a
# named path rule, never a narrowed find.
root="${MACHO_ROOT:-${UNSLOTH_STUDIO_HOME:-$HOME/.unsloth}}"
want="$(uname -m)"
[ "$want" = "aarch64" ] && want=arm64
if [ ! -d "$root" ]; then
fail "macho requested but $root does not exist"
else
# SCOPE, part 2: the two payloads the install RUNS ON live outside $root.
# `uv venv` links <venv>/bin/python at its base interpreter rather than copying
# it, and the find below has no -L, so the interpreter that ran every install
# step is invisible to it; the uv that fetched it lands in $HOME/.local/bin.
# Both are exactly what Rosetta 2 hides: an x86_64 uv or managed CPython runs
# green here and dies on the factory-fresh Mac this job stands in for.
_macho_targets() {
find "$root" -type f \( -perm -u+x -o -name '*.dylib' -o -name '*.so' -o -name '*.node' \) 2>/dev/null
# -L follows the interpreter symlink; -maxdepth keeps this a bin/ lookup and
# not a second walk of site-packages through the venv's lib64 link. Depth 4
# covers <root>/unsloth_studio, the .venv_t5_* sidecars and the tauri layout's
# <root>/studio/unsloth_studio.
find -L "$root" -maxdepth 4 -type f -path '*/bin/python' 2>/dev/null
for _uv in "$HOME/.local/bin/uv" "$(command -v uv 2>/dev/null || true)"; do
[ -n "$_uv" ] && [ -f "$_uv" ] && printf '%s\n' "$_uv"
done
}
n=0 nexe=0 nout=0 bad_arch="" unsigned="" broken=""
while IFS= read -r f; do
desc="$(file -b "$f" 2>/dev/null || true)"
case "$desc" in *Mach-O*) ;; *) continue ;; esac
n=$((n + 1))
case "$f" in "$root"/*) ;; *) nout=$((nout + 1)) ;; esac
# Substring, not equality: a universal binary lists every slice it carries,
# and one that includes the host arch is fine.
case "$desc" in
*"$want"*) ;;
*) bad_arch="$bad_arch $f [$desc]" ;;
esac
# Signature: MAIN EXECUTABLES ONLY. Asserting it on every Mach-O failed the
# mask/pipe leg on 29 ordinary PyPI extension modules plus libportaudio.dylib:
# MH_BUNDLE/MH_DYLIB images dlopen'd without library validation, shipped
# unsigned, and that run had already imported them with the installer exiting
# 0. macOS enforces on main executables and gatekept .app bundles.
#
# Key off the filetype `file` reports, not the path: a .so may be a bundle or a
# dylib, and an executable may have no extension. The library veto is second so
# a mixed-type fat file counts as a library. Substring tests are
# order-independent (Apple prints `... executable arm64`, GNU `... arm64
# executable`).
_is_exe=0
case "$desc" in *executable*) _is_exe=1 ;; esac
case "$desc" in *"shared library"*|*bundle*) _is_exe=0 ;; esac
# Named rule so a failure says which path matched; the filetype test
# already covers the MH_EXECUTE at .app/Contents/MacOS/<name>.
case "$f" in *.app/Contents/MacOS/*) _is_exe=1 ;; esac
[ "$_is_exe" = 1 ] && nexe=$((nexe + 1))
# arm64 only: the kernel refuses to exec an unsigned arm64 main binary
# ("Killed: 9"), while x86_64 execs it happily, so an unsigned x86_64
# payload is not the same defect.
if [ "$want" = "arm64" ] && [ "$_is_exe" = 1 ]; then
# Ad-hoc counts as signed: arm64 linkers seal ad-hoc by default, so the test
# is "has a seal that verifies", not "has an identity". `spctl` and
# `--strict` would demand an authority and reject ad-hoc.
if ! codesign -v "$f" >/dev/null 2>&1; then
# Nothing to verify and a seal that does not match mean different things.
# Captured, not piped into grep: `codesign -dvv` exits non-zero on an
# unsigned file, and under `pipefail` that is the pipeline's status even
# on a match.
_sig="$(codesign -dvv "$f" 2>&1 || true)"
case "$_sig" in
*"not signed at all"*) unsigned="$unsigned $f" ;;
*) broken="$broken $f" ;;
esac
fi
fi
done < <(_macho_targets | sort -u)
if [ "$n" = "0" ]; then
# An empty scan reads exactly like a clean one, so a wrong root would pass
# and prove nothing.
fail "no Mach-O found under $root; the arch/signature assertion proved nothing"
elif [ "$nout" = "0" ]; then
# Same rule for the roots added above: install.sh always bootstraps uv into
# $HOME/.local/bin, so zero hits outside $root means the extra scan matched
# nothing and uv's architecture went unproven.
fail "no Mach-O outside $root was scanned, so uv and the venv's base interpreter escaped the check"
elif [ -n "$bad_arch" ]; then
fail "Mach-O is not $want, so it runs here only under Rosetta 2, which a fresh Mac does not have:$bad_arch"
elif [ -n "$unsigned" ]; then
fail "unsigned Mach-O main executable, which arm64 macOS refuses to exec:$unsigned"
elif [ -n "$broken" ]; then
fail "Mach-O main executable carries a signature that does not verify:$broken"
else
ok "$n Mach-O files under $root, plus uv and the venv's base interpreter, are $want$([ "$want" = arm64 ] && echo "; all $nexe main executable(s) signed")"
fi
fi
;;
*)
fail "unknown check '$check'"
;;
esac
done
exit "$rc"