Docker Studio: keep unsloth-studio-update --ref from leaving Studio dead - #10826
Docker Studio: keep unsloth-studio-update --ref from leaving Studio dead#10826oobabooga wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e11d8c097e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| [ -x "$SUPCTL" ] && { "$SUPCTL" status studio >/dev/null 2>&1 || _st=$?; } || _st=4 | ||
| if [ "$_st" = "0" ] || [ "$_st" = "3" ]; then | ||
| log "restarting the studio service" | ||
| "$SUPCTL" restart studio || true |
There was a problem hiding this comment.
Propagate a failed Studio restart
When supervisorctl restart studio returns nonzero, this line discards the failure; the subsequent unreachable health check is only a warning, and with the documented UNSLOTH_STUDIO_UPDATE_HEALTH_WAIT=0 it is skipped entirely, so the updater prints done and exits successfully. Because the previous source tree has already been deleted, a ref that imports successfully but fails during actual startup can still leave Studio in FATAL with no rollback while callers believe the update succeeded; retain the rollback tree and return nonzero or restore it when the restart cannot be confirmed.
Useful? React with 👍 / 👎.
| log "installing frontend dependencies" | ||
| npm ci --no-fund --no-audit --loglevel=error || npm install --no-fund --no-audit --loglevel=error | ||
| log "building the frontend" | ||
| npm run build |
There was a problem hiding this comment.
Stop when the frontend build command fails
When npm run build creates dist/index.html but subsequently exits nonzero—for example, a requested ref adds a failing post-build step—the update still succeeds. The surrounding subshell is the left side of || return 1, so Bash disables errexit for commands inside it; the following if can return zero, and the later file-existence check accepts the partial build. Check the build command's status explicitly before continuing so a failed ref build is never swapped into the live source tree.
Useful? React with 👍 / 👎.
| mv "$PREV_SRC" "$SRC" | ||
| SWAPPED=0 | ||
| fi | ||
| "$PY" -m pip install --no-deps -r "$ROLLBACK" >/dev/null \ |
There was a problem hiding this comment.
Restore dependencies after a
--with-deps failure
When an update is run with --with-deps, pip may upgrade any dependency before either pip itself or studio_tree_ok reports failure, but the rollback manifest records only unsloth and unsloth_zoo and this reinstall explicitly uses --no-deps. Reinstalling those two packages therefore cannot restore the previous environment; an upgraded incompatible dependency can keep the old Studio unable to import or start even though the failure path claims the previous install is back. Snapshot and restore the affected dependency versions as well when dependency updates are enabled.
Useful? React with 👍 / 👎.
| [ -n "$STAGE" ] && rm -rf "$STAGE" | ||
| rm -f "$ROLLBACK" | ||
| } | ||
| trap cleanup EXIT |
There was a problem hiding this comment.
Restore the swapped tree when the updater is interrupted
If the updater receives a trappable signal such as TERM or HUP after the source swap but while pip is still installing the Zoo ref, the EXIT trap runs only cleanup: it deletes the rollback manifest without calling restore, leaving the unvalidated new tree active and the previous tree stranded at .src-prev.*. A container shutdown or command timeout in this window can therefore leave lazy imports or the next Studio restart using a mismatched source/Zoo combination; make the exit trap restore while SWAPPED=1 and disarm that rollback only after the update is committed.
Useful? React with 👍 / 👎.
|
Confirmed this hits docker/unsloth_studio_update.sh, where the post-update check only imports studio.backend.main and the supervisorctl exit 3 path reports Studio as unmanaged, so --ref can leave it FATAL. Will get this reviewed. |
unsloth-studio-update --ref main, the command the helper's own--helpshows, kills Studio in the container it was asked to update. Studio never comes back, not even afterdocker restart, and running the helper again says it cannot help. This makes the command work, and makes every failure put the previous install back instead of leaving a dead container.Why it breaks
--refrunspip install git+https://github.com/unslothai/unsloth@main, which replaces the editable install at/opt/unsloth-studio/srcwith a build insite-packages. Only release wheels carry the built frontend, sounsloth studioexits 1 on start:The helper only checked
import studio.backend.main, which still succeeds, so it restarted Studio into that tree. supervisord retries three times and parks the program inFATAL.Two things then make it worse:
supervisor not managing 'studio' here; restart Studio yourself.supervisorctl statusexits 3 for a program that exists but is not running, and the script read that as "not managed".The fix
--refnow installs the same shape the image was built with instead of a wheel: the ref is fetched into a staging tree next tosrc, its frontend is built there with the bundled Node (npm ci, falling back tonpm install, thennpm run build), theoxc-validatorruntime is installed, and only then is the tree swapped in and installed editable. Nothing is touched until the build succeeds.srcto where it really is (readlink -f), so an image that presents it as a link into its own copy of Studio gets the update in that copy rather than a directory written over the link.import studio.backend.mainand astudio/frontend/dist/index.htmlnext to the imported package. The checks run from/, so astudiodirectory in the caller's working directory cannot answer for the installed one.unslothandunsloth_zooare installed before it starts (fromdirect_url.json: an editable path, a git commit, or a released version), puts the old source tree back, reinstalls exactly those, says so, and exits non-zero without restarting.supervisorctl statusexit 3 now counts as managed, so a Studio leftFATALby an earlier update gets restarted instead of a "not managing" message./api/healthand says whether Studio came back.UNSLOTH_STUDIO_UPDATE_HEALTH_WAITbounds the wait (default 180 seconds, 0 skips it).Before and after
Both sides are
unsloth/unsloth:latest; B carries this branch's helper. Same container recipe, no GPU.--ref mainFATAL,/api/healthunreachableRUNNING, health 200--ref main, Playwright--ref mainsite-packagescopy without a frontend/opt/unsloth-studio/src,inference.pymd55ff793a5to0ad553ae(main)studio/frontend/distandoxc-validator/node_modulesafter the updateFATALFATAL--packages unsloth==2025.1.1)FATALRUNNINGbefore and after a restart--ref no-such-ref-xyzRUNNINGThe
--ref mainrun costs about 80 seconds, most of it the frontend build.Verification
tests/python/test_docker_update_helpers.py: 25 passed. The Studio helper's cases run the real script against a fakestudiopackage, with pip, supervisorctl, git and the bundled npm as recording stubs, so the import check, the dist check and the rollback are exercised rather than asserted from source text. The linked-srccase is covered too: the update must land in the tree the link points at, with no staging directory left in the Studio home.main: the dist check, the rollback, theFATALrestart, the two--refbuild cases and the rollback message. Run them from outside the repository: from the repo root,main'simport studio.backend.mainpicks up the working tree'sstudio/and fails for an unrelated reason.Not in this PR
This is the in-container helper only.
unsloth studio updateand the installer are unchanged.--refneeds the bundled Node and network access for the frontend build. Without$UNSLOTH_STUDIO_HOME/node/bin/npmit refuses, rather than installing a tree Studio cannot serve.A release-wheel update still installs into
site-packages, where the wheel carriesfrontend/distbut nooxc-validator/node_modules, so code validation in Data Recipes degrades there as it does for any wheel install. Pre-existing, and separate from this failure.The update still lives in the container's writable layer, so it survives
docker restartand notdocker rm. Pulling a new image is still the lasting path.