From a681c778bdf70bbc67f3b44d2aad98079a1eb2f9 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Fri, 3 Apr 2026 13:44:58 +0000 Subject: [PATCH] Fix P1 review issues: race condition, fast-path bypass, git revert, timestamp guard 1. Fix cold-start race in /api/update-check: if the background thread has not finished fetching the manifest when the endpoint is hit, do an inline fetch so the UI never permanently caches empty defaults. 2. Fix PyPI fast-path blocking manifest git-main installs: evaluate the manifest unsloth_source directive BEFORE the PyPI version check so users already on latest PyPI still switch to git main when directed. 3. Fix base.txt reverting git install: filter out "unsloth" from base.txt requirements when the git-main path is active, preventing pip/uv from replacing the git checkout with the PyPI wheel. 4. Guard install timestamp write: only write UNSLOTH_STUDIO_INFO.json when python deps were actually updated, preventing users from clearing a critical-update warning by re-running setup in llama-only or skip-python-deps mode. --- studio/backend/main.py | 5 ++++- studio/install_python_stack.py | 20 ++++++++++++++------ studio/setup.sh | 25 ++++++++++++++++++------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/studio/backend/main.py b/studio/backend/main.py index 5a573f26a5..8942ffd74f 100644 --- a/studio/backend/main.py +++ b/studio/backend/main.py @@ -225,9 +225,12 @@ async def health_check(): @app.get("/api/update-check") async def update_check(): """Return cached update status from the remote manifest (unauthenticated).""" - from utils.update_check import get_update_status + from utils.update_check import fetch_and_cache_update_status, get_update_status s = get_update_status() + if not s.manifest_fetched: + # Background thread may not have finished yet -- fetch inline. + s = fetch_and_cache_update_status() return { "critical": s.critical, "announcement_badge": s.announcement_badge, diff --git a/studio/install_python_stack.py b/studio/install_python_stack.py index d8903c7d5e..5ab3c3c7d8 100644 --- a/studio/install_python_stack.py +++ b/studio/install_python_stack.py @@ -534,13 +534,21 @@ def install_python_stack() -> int: f"git+https://github.com/unslothai/unsloth.git@{_git_ref}", constrain = False, ) - pip_install( - "Updating remaining base packages", - "--no-cache-dir", - "--upgrade-package", - "unsloth-zoo", - req = REQ_ROOT / "base.txt", + # Filter out "unsloth" from base.txt so pip does not + # revert the git install back to the PyPI wheel. + _base_no_unsloth = _filter_requirements( + REQ_ROOT / "base.txt", {"unsloth"} ) + try: + pip_install( + "Updating remaining base packages", + "--no-cache-dir", + "--upgrade-package", + "unsloth-zoo", + req = _base_no_unsloth, + ) + finally: + _base_no_unsloth.unlink(missing_ok = True) else: # Update path: upgrade only unsloth + unsloth-zoo while preserving # existing torch/CUDA installations. Torch is pre-installed by diff --git a/studio/setup.sh b/studio/setup.sh index d16920f9e9..90a84f32cb 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -506,8 +506,22 @@ _SKIP_VERSION_CHECK=false if [ "$_COLAB_NO_VENV" = true ]; then _SKIP_VERSION_CHECK=true fi + +# Apply manifest: install from git main instead of PyPI when directed. +# Must be evaluated BEFORE the PyPI version check so the fast-path +# does not suppress the git-main install. +_MANIFEST_FORCE_GIT=false +if [ "$MANIFEST_UNSLOTH_SOURCE" = "main" ] && [ "${STUDIO_LOCAL_INSTALL:-0}" != "1" ]; then + export STUDIO_UNSLOTH_GIT_REF="${MANIFEST_UNSLOTH_GITHUB_REF:-main}" + _MANIFEST_FORCE_GIT=true + substep "manifest override: installing unsloth from git@${STUDIO_UNSLOTH_GIT_REF}" +fi + _PKG_NAME="${STUDIO_PACKAGE_NAME:-unsloth}" -if [ "$_SKIP_VERSION_CHECK" != true ] && [ "${SKIP_STUDIO_BASE:-0}" != "1" ] && [ "${STUDIO_LOCAL_INSTALL:-0}" != "1" ]; then +if [ "$_MANIFEST_FORCE_GIT" != true ] && \ + [ "$_SKIP_VERSION_CHECK" != true ] && \ + [ "${SKIP_STUDIO_BASE:-0}" != "1" ] && \ + [ "${STUDIO_LOCAL_INSTALL:-0}" != "1" ]; then # Only check when NOT called from install.sh (which just installed the package) INSTALLED_VER=$("$VENV_DIR/bin/python" -c " from importlib.metadata import version @@ -529,11 +543,6 @@ print(version('$_PKG_NAME')) fi if [ "$_SKIP_PYTHON_DEPS" = false ]; then - # Apply manifest: install from git main instead of PyPI when directed - if [ "$MANIFEST_UNSLOTH_SOURCE" = "main" ] && [ "${STUDIO_LOCAL_INSTALL:-0}" != "1" ]; then - export STUDIO_UNSLOTH_GIT_REF="${MANIFEST_UNSLOTH_GITHUB_REF:-main}" - substep "manifest override: installing unsloth from git@${STUDIO_UNSLOTH_GIT_REF}" - fi install_python_stack # ── 6b. Pre-install transformers 5.x into .venv_t5/ ── @@ -1039,7 +1048,8 @@ else } fi # end _SKIP_GGUF_BUILD check -# ── Write install timestamp ── +# ── Write install timestamp (only when python deps were actually updated) ── +if [ "$_SKIP_PYTHON_DEPS" = false ]; then _STUDIO_INFO_DIR="$HOME/.unsloth/studio" mkdir -p "$_STUDIO_INFO_DIR" python - "$_STUDIO_INFO_DIR/UNSLOTH_STUDIO_INFO.json" <<'PY' 2>/dev/null || true @@ -1067,6 +1077,7 @@ existing["unsloth_source"] = os.environ.get("STUDIO_UNSLOTH_GIT_REF", "pypi") info_path.write_text(json.dumps(existing, indent=2) + "\n", encoding="utf-8") PY verbose_substep "wrote install info to $_STUDIO_INFO_DIR/UNSLOTH_STUDIO_INFO.json" +fi # ── Footer ── if [ "$_LLAMA_ONLY" = "1" ]; then