From 3e7fc344bbd2d09016f782e285f6fa8fb0315f92 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 28 Jul 2026 20:01:10 +0000 Subject: [PATCH] Make the NO_CLI legs assert repair, and fix the Windows straggler sweep Two of the interrupted-install legs were passing without testing anything. The re-run assertion skipped verdict=NO_CLI, but a kill at "venv" or "torch" lands before install.sh ever prints "Installing Unsloth" (:2125, :3667, :3961), so those legs can only ever produce NO_CLI. Three non-gating-exempt cells (macos-14 kill@venv, macos-14 kill@torch, ubuntu-latest kill@torch) therefore asserted nothing beyond a marker appearing in a log. NO_CLI is now included: a re-run must produce a booting backend regardless of how little the first run managed to install. Each re-run step grows an existence check first, because the probe exits without writing verdict.json when the binary is absent and the json.load would crash rather than report. The Windows straggler sweep matched nothing at all. UNSLOTH_STUDIO_HOME arrives as D:\a\r\r/.studio-home, since the workflow joins ${{ github.workspace }} with a forward slash, while Process.Path is all backslashes, so the literal -like missed even the venv's own python.exe. uv is never under the studio home in any case: install.ps1 takes it from winget or astral.sh. Normalise the separators, match uv by name (the runner is ephemeral and runs no other uv), and skip the home comparison entirely when the variable is empty, which would otherwise turn the pattern into "**" and kill every python on the runner. --- .github/scripts/interrupt-install.ps1 | 21 +++++++++++---- .github/workflows/interrupted-install-ci.yml | 28 +++++++++++++++----- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/.github/scripts/interrupt-install.ps1 b/.github/scripts/interrupt-install.ps1 index 0cdafed048..6860ad0107 100644 --- a/.github/scripts/interrupt-install.ps1 +++ b/.github/scripts/interrupt-install.ps1 @@ -81,11 +81,22 @@ if (-not $killed -and -not $proc.HasExited) { if (-not $reason) { $reason = 'dea if ($killed) { Write-Host "[interrupt] killing process tree of $($proc.Id) ($reason)" Stop-Tree $proc.Id - # Any straggler uv/python that reparented away from the installer. - foreach ($name in 'uv', 'python') { - Get-Process -Name $name -ErrorAction SilentlyContinue | - Where-Object { $_.Path -and $_.Path -like "*$env:UNSLOTH_STUDIO_HOME*" } | - ForEach-Object { try { Stop-Process -Id $_.Id -Force } catch { } } + # Any straggler uv/python that reparented away from the installer. The old sweep + # matched nothing: UNSLOTH_STUDIO_HOME arrives as `D:\a\r\r/.studio-home` + # (github.workspace joined with a forward slash) while Process.Path is all + # backslashes, so the literal -like missed even the venv's own python, and uv is + # never under the studio home anyway (install.ps1 takes it from winget or + # astral.sh). Normalise the separators, and take uv by name since the runner is + # ephemeral and runs no other uv. + $homeNorm = if ([string]::IsNullOrWhiteSpace($env:UNSLOTH_STUDIO_HOME)) { $null } + else { ($env:UNSLOTH_STUDIO_HOME -replace '/', '\').TrimEnd('\') } + foreach ($p in @(Get-Process -Name 'uv', 'python', 'pythonw' -ErrorAction SilentlyContinue)) { + $path = $null + try { $path = $p.Path } catch { } + $inHome = $homeNorm -and $path -and ($path -like "$homeNorm\*") + if ($p.ProcessName -eq 'uv' -or $inHome) { + try { Stop-Process -Id $p.Id -Force; Write-Host "[interrupt] swept $($p.ProcessName) pid=$($p.Id)" } catch { } + } } } diff --git a/.github/workflows/interrupted-install-ci.yml b/.github/workflows/interrupted-install-ci.yml index 358141f456..c59b31fdd2 100644 --- a/.github/workflows/interrupted-install-ci.yml +++ b/.github/workflows/interrupted-install-ci.yml @@ -151,9 +151,12 @@ jobs: exit "$rc" - name: A re-run must repair, not short-circuit - # Only meaningful when the install is broken but present. The bug's second half - # is that `install.sh` sees a "current" version and no-ops over a broken venv. - if: always() && steps.probe.outputs.verdict != 'NO_CLI' && steps.probe.outputs.verdict != 'HEALTHY' + # NO_CLI included. A kill at venv or torch lands before "Installing Unsloth" + # (install.sh:2125 / :3667 / :3961), so those legs always take NO_CLI, and + # skipping the re-run left three non-experimental legs asserting nothing but + # that a marker appeared. The bug's second half is that `install.sh` sees a + # "current" version and no-ops over a broken venv. + if: always() && steps.probe.outputs.verdict != 'HEALTHY' run: | set -o pipefail rc=0 @@ -161,6 +164,13 @@ jobs: echo "repair exit: $rc" BIN="$HOME/.unsloth/studio/unsloth_studio/bin/unsloth" [ -x "$BIN" ] || BIN="$HOME/.unsloth/studio/bin/unsloth" + # The probe exits without writing verdict.json when the bin is missing, so + # check here or the json.load below crashes instead of reporting. + if [ ! -x "$BIN" ]; then + echo "::error::after a full re-run there is still no unsloth CLI at $BIN" + tail -30 logs/repair.log || true + exit 1 + fi python3 .github/scripts/interrupted_install_probe.py "$BIN" --out probe-after || true v="$(python3 -c "import json;print(json.load(open('probe-after/verdict.json'))['verdict'])")" # A booting backend IS the repair, whatever the log narrated. Judging by @@ -259,15 +269,21 @@ jobs: exit $rc - name: A re-run must repair, not short-circuit - # Same assertion the POSIX legs make. Without it a Windows leg proves only that + # Same assertion the POSIX legs make, NO_CLI included: a leg that left no CLI + # otherwise asserts nothing, and without this a Windows leg proves only that # the break was DETECTED, never that install.ps1's version fast path does not - # short-circuit over it -- which is the half of the bug that strands the user. - if: always() && steps.probe.outputs.verdict != 'NO_CLI' && steps.probe.outputs.verdict != 'HEALTHY' + # short-circuit over it, which is the half of the bug that strands the user. + if: always() && steps.probe.outputs.verdict != 'HEALTHY' shell: pwsh run: | pwsh -NoProfile -NonInteractive -File install.ps1 ${{ matrix.installArgs }} *>&1 | Tee-Object -FilePath logs/repair.log $bin = Join-Path $env:UNSLOTH_STUDIO_HOME 'unsloth_studio\Scripts\unsloth.exe' + if (-not (Test-Path $bin)) { + Write-Host "::error::after a full re-run there is still no unsloth CLI at $bin" + Get-Content logs/repair.log -Tail 30 -ErrorAction SilentlyContinue + exit 1 + } python .github/scripts/interrupted_install_probe.py $bin --out probe-after $v = (Get-Content probe-after/verdict.json -Raw | ConvertFrom-Json).verdict # A booting backend IS the repair, whatever the log narrated. Judging by