Three small but high-signal changes that came out of an audit of how
much Studio surface CI actually exercises:
1. Every studio-*-smoke.yml workflow now uploads its artifacts on
`if: always()` instead of `if: failure()`. On green runs the
screenshots + studio.log are now reviewable in the Actions UI,
which closes the "passed but the UI is silently broken" hole.
SHA-pinned to actions/upload-artifact@v4.6.2 across all 7 upload
steps (was a mix of @v4 unpinned + the SHA-pin).
2. /api/system and /api/system/hardware now require a Bearer token
(Depends(get_current_subject)). Today they leak Python version,
GPU name, total memory, and the ML package set without auth --
fine on a single-user Tauri box, not fine on -H 0.0.0.0 / Colab
/ a Tauri-relayed setup. /api/system/gpu-visibility was already
gated; now /api/system + /api/system/hardware match it.
3. Path filters + health-wait plumbing:
- studio-ui-smoke.yml now triggers on tests/studio/** so a PR
that ONLY edits the Playwright test file actually runs UI CI.
- studio-tauri-smoke.yml now triggers on unsloth_cli/** so a CLI
rename or signature change that breaks Tauri's spawned
`unsloth studio` actually runs Tauri CI.
- The 60s `/api/health` wait loop in studio-ui-smoke.yml +
studio-inference-smoke.yml (3 jobs) is now 180s. Cold runners
with venv warm-up + lazy imports have been observed exceeding
60s, and the cost of a false-fail is much higher than two
extra minutes of waiting.
150 lines
5.9 KiB
YAML
150 lines
5.9 KiB
YAML
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
|
|
|
# Verifies that `unsloth studio update --local` is idempotent: a fresh
|
|
# install via install.sh, followed by `unsloth studio update --local`,
|
|
# succeeds and is a no-op for the llama.cpp prebuilt (it should report
|
|
# "prebuilt up to date and validated", not re-run the source build).
|
|
#
|
|
# This catches regressions in setup.sh's update path that the existing
|
|
# GGUF / wheel jobs would miss because they only invoke install.sh once.
|
|
|
|
name: Studio Update CI
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'install.sh'
|
|
- 'studio/setup.sh'
|
|
- 'studio/install_python_stack.py'
|
|
- 'studio/install_llama_prebuilt.py'
|
|
- 'studio/backend/requirements/**'
|
|
- 'unsloth_cli/commands/studio.py'
|
|
- 'pyproject.toml'
|
|
- '.github/workflows/studio-update-smoke.yml'
|
|
push:
|
|
branches: [main, pip]
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
update-idempotency:
|
|
name: Studio Updating Tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
|
|
- name: Linux deps for llama.cpp prebuilt
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
libcurl4-openssl-dev libssl-dev jq
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: '22'
|
|
cache: 'npm'
|
|
cache-dependency-path: studio/frontend/package-lock.json
|
|
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: '3.12'
|
|
cache: 'pip'
|
|
|
|
- name: Install Studio (--local, --no-torch)
|
|
# Pass the workflow token so the llama.cpp prebuilt installer's
|
|
# GitHub-API call to list releases isn't rate-limited (60/hr
|
|
# unauthenticated). Without this, three consecutive install +
|
|
# update + update calls in this job exceed the limit and the
|
|
# prebuilt path falls back to source build.
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
mkdir -p logs
|
|
set -o pipefail
|
|
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
|
|
|
|
- name: First update should be a no-op (prebuilt already validated)
|
|
# `unsloth studio update --local` runs studio/setup.sh against
|
|
# the local repo. Right after install.sh the llama.cpp prebuilt
|
|
# has just been installed and validated, so the second run must
|
|
# take the "prebuilt up to date and validated" code path. Any
|
|
# source-build fallback or re-download here means setup.sh's
|
|
# idempotency regressed.
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -o pipefail
|
|
unsloth studio update --local 2>&1 | tee logs/update.log
|
|
if grep -q "falling back to source build" logs/update.log; then
|
|
echo "::error::studio update fell back to source-build llama.cpp on a fresh install. setup.sh idempotency regressed."
|
|
grep -E "llama-prebuilt|llama.cpp" logs/update.log | tail -60
|
|
exit 1
|
|
fi
|
|
if ! grep -qE "prebuilt up to date and validated|prebuilt installed and validated" logs/update.log; then
|
|
echo "::error::no prebuilt up-to-date marker in update.log. Did setup.sh skip the prebuilt path on update?"
|
|
grep -E "llama-prebuilt|llama.cpp" logs/update.log | tail -60
|
|
exit 1
|
|
fi
|
|
echo "update path took the prebuilt fast path"
|
|
|
|
- name: Second update must also be a no-op
|
|
# Two consecutive `update`s back-to-back is the usual desktop
|
|
# flow (auto-update, then user-triggered update). Asserting the
|
|
# second run is also clean rules out hidden state changes from
|
|
# the first one.
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -o pipefail
|
|
unsloth studio update --local 2>&1 | tee logs/update2.log
|
|
grep -q "falling back to source build" logs/update2.log && {
|
|
echo "::error::second update fell back to source build"
|
|
tail -60 logs/update2.log; exit 1; } || true
|
|
grep -qE "prebuilt up to date and validated|prebuilt installed and validated" logs/update2.log
|
|
echo "second update was clean"
|
|
|
|
- name: Boot Studio briefly to confirm the install is still usable
|
|
# If `update --local` accidentally broke the venv or wiped the
|
|
# llama-server binary, the server would fail to start here.
|
|
run: |
|
|
mkdir -p logs
|
|
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18891 \
|
|
> logs/studio.log 2>&1 &
|
|
PID=$!
|
|
for i in $(seq 1 60); do
|
|
if curl -fs http://127.0.0.1:18891/api/health > /tmp/health.json; then
|
|
jq -e '.status == "healthy"' /tmp/health.json
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if ! jq -e '.status == "healthy"' /tmp/health.json 2>/dev/null; then
|
|
echo "Studio failed to come up after `update`"
|
|
tail -200 logs/studio.log
|
|
kill "$PID" 2>/dev/null || true
|
|
exit 1
|
|
fi
|
|
kill "$PID" 2>/dev/null || true
|
|
echo "post-update Studio /api/health OK"
|
|
|
|
- name: Upload update logs
|
|
# Always upload so a green run still leaves the install + two
|
|
# update logs reviewable.
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: studio-update-log
|
|
path: |
|
|
logs/install.log
|
|
logs/update.log
|
|
logs/update2.log
|
|
logs/studio.log
|
|
retention-days: 7
|