unsloth/tests/studio
Daniel Han 1781770bee
Studio: detect an interrupted dependency install instead of launching a backend that cannot import (#7492)
* Studio: detect an interrupted dependency install instead of launching a backend that cannot import

An installer killed part-way leaves a venv with a working CLI but without
studio.txt's dependencies. Nothing recorded that, so three separate places all
reported it healthy:

- the desktop preflight probed only `unsloth -h` (typer + rich) and a hardcoded
  desktop-capabilities dict, neither of which touches studio.backend, so it
  returned ManagedReady and spawned a backend that died on `import structlog`;
- setup.sh's fast path compared the installed unsloth version against PyPI,
  which matches on a half-built venv because unsloth is installed early, so
  `unsloth studio update` printed "up to date" and repaired nothing;
- start_managed_repair calls that update and then re-checks with the same blind
  probes, so Repair reported success without fixing anything.

install_python_stack.py now clears a completion manifest before the dependency
pass and writes it only after the final step. `unsloth studio verify-install`
and desktop-capabilities' new studio_install_ok field read it, the preflight
turns a false answer into ManagedStale so auto-repair runs, and setup.sh /
setup.ps1 gain an escape hatch next to the existing anyio one.

Separately, the wheel ships studio/ and studio.backend* but declared none of
their dependencies, so `unsloth train`, `export`, `chat`, `inference` and
`studio` all ended in a rich traceback after a plain pip install. structlog is
the only hard module-level import that chain reaches once starlette's
annotation-only import moves under TYPE_CHECKING, so it becomes a core
dependency and the rest of the server stack becomes a [studio] extra mirroring
studio.txt. The CLI import sites now report missing dependencies as a sentence
with two remedies.

Fixes #4701, #5260, #7147

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Match the trimmed comments merged on the pip branch

* Put the install manifest in the preflight fingerprint for PR #7492

The capability cache keyed the venv on pyvenv.cfg, uv.lock, requirements.txt,
the interpreter and site-packages/unsloth_cli/commands/studio.py, none of which
a repair touches when it only reinstalls studio.txt. So an entry cached while
the install was healthy stayed valid after the manifest was dropped, and the
probe returned Ready on exactly the half-built venv this is meant to catch.

* Address the review findings on PR #7492

Fail the install when the completion manifest cannot be written, instead of
exiting 0 without the record every later check requires, which is a repair
loop by construction.

Compare the version of the package the manifest names, so `studio update
--package X` does not read as a permanent version change.

Read the manifest from the venv that owns it when the CLI runs outside the
managed venv, and drop the dependency verdict in that case: the walk ran
against the wrong interpreter and says nothing about that venv.

Name the import that actually failed. `unsloth train` reaches torch through
the same guard, and the studio extra does not carry it, so recommending that
extra alone left the command failing in the same place.

* Declare click, which typer stopped providing, for PR #7492

unsloth_cli/commands/start.py imports click at module scope and
unsloth_cli/__init__.py imports that module, so every unsloth command needs
it. typer carried click through 0.19 and dropped it in 0.27, and the declared
floor is typer>=0.12.0, so a fresh resolve gets no click. On the published
wheel it still arrives because huggingface_hub requires click<9,>=8.4.2, which
is luck rather than a declaration. A wheel built from this branch's
dependency list has neither, and every command dies at import.

Verified: before, `unsloth --help` on a fresh venv raised ModuleNotFoundError
for click; after, it exits 0. The drift test now covers it.

* Keep a running backend from the previous app version manageable

The manageability bump gated two unrelated things through one constant. For
the managed CLI probe 2 is right: a CLI reporting 1 cannot answer
studio_install_ok. For a RUNNING backend it is wrong, because a process
already started cannot change what it reports, so bumping studio/backend/main.py
in lockstep does not help one the previous app version spawned.

That backend is proven ours by root id and ownership token, but
lifecycle_control_block_reason returned Unmanageable, and that branch never
calls adopt_verified_backend. has_owned_backend() stays false, so Repair falls
into block_external_conflict, which finds the same process and refuses: the app
could no longer stop a backend it owns the token for. The same regression in
backend.rs turned a terminal-launched same-root server from AttachedReady into
ExternalConflict.

Split the constant: DESKTOP_BACKEND_MANAGEABILITY_VERSION = 1 for the two
live-backend probes, DESKTOP_MANAGEABILITY_VERSION = 2 for the CLI probe. Every
real gate (protocol, auth, ownership, desktop-login, MIN_DESKTOP_BACKEND_VERSION)
is untouched, so an old backend still reaches OwnedStale, adopt, stop, repair.

Also stop the installer when the stale manifest cannot be removed. Windows
raises on a read-only or locked file, and the pass would then run behind a
marker that still names this version and these digests, so a run killed
part-way would verify as complete.

* Answer for the managed venv, not the one the CLI happens to run in

The guard matched ModuleNotFoundError.name, an import name, against
missing_requirements(), which returns distribution names. So a missing PyJWT
printed 'pip install jwt', and jwt, docx and fitz are each a real but unrelated
PyPI project (fitz is a neuroimaging workflow tool), so following the advice
installed the wrong package and left the backend just as broken. Map the import
to its distribution before deciding, and never offer the import itself.

install_state() verified the caller's own prefix. The wheel ships studio/, so a
CLI installed outside the managed venv always finds its own copy of the helper
first, and a healthy managed install reported studio_install_incomplete with a
missing list copied from the wrong venv. Selecting the root is not enough:
_installed_version() reads the running interpreter and req_root defaults to the
caller's studio.txt, so both checks still answered for the wrong venv. Hand
verify_install() that venv's own metadata, enumerated through
Distribution.discover(context = ...path), which does not fall back to sys.path.
The candidate order is untouched, so shadowed-tree detection is unchanged.

setup.ps1 replaces pip, torch and triton before install_python_stack.py runs,
so the manifest it drops is not dropped before the first mutation. A run killed
in between kept a marker that still verifies while torch was half-replaced;
drop it at the top of the dependency pass instead. setup.sh is unaffected, the
stack is the first thing its pass runs, and a test now pins both.

pip uninstall rewrites nothing that was fingerprinted, and cache_matches
re-reads the cached studio_install_ok rather than re-checking, so a venv that
lost a studio.txt package kept being served the healthy verdict. Fold a sorted
hash of the installed dist-info names into the marker hash.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* A missing manifest helper is a torn install, not an old one

studio/install_manifest.py ships in the same wheel as _studio_deps.py, so
nothing legitimately has one without the other: a CLI predating both never
reaches this code, and the desktop already calls such a CLI stale on
desktop_manageability_version.

Returning ok=true there reported a healthy install for a tree the package
update had half replaced, and the preflight then launched a backend whose
own run.py could be just as absent. Report it incomplete so repair runs.

* Tighten comments across the install-detection changes

* Validate Studio dependency readiness

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: Wasim Yousef Said <wasimysdev@gmail.com>
2026-07-28 10:57:20 +02:00
..
install Studio: detect an interrupted dependency install instead of launching a backend that cannot import (#7492) 2026-07-28 10:57:20 +02:00
load_freeze tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
_playwright_robust.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
playwright_chat_ime_i18n.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
playwright_chat_ui.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
playwright_extra_ui.py Studio UI test: recover from voice-picker renderer crash, scoped to macOS runners 2026-07-23 21:54:41 -07:00
playwright_model_config.py fix(studio): honor run settings on initial model load (#7346) (#7351) 2026-07-24 23:27:47 -07:00
playwright_ui_font_scale.py Fix the CPU-only ROCm routing errors and two font-scale UI flakes (#7469) 2026-07-26 04:48:49 -07:00
run_real_mlx_smoke.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
studio_api_smoke.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_auth_form_input_count.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_cached_model_path_selection.py Feat/model picker per model config v2 (#7207) 2026-07-20 22:53:22 -07:00
test_cancel_atomicity.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_cancel_id_wiring.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_chat_preset_builtin_invariants.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_chat_preset_load_config.py feat(studio): presets include load settings (#7347) (#7352) 2026-07-24 02:23:43 -07:00
test_chat_prompt_variables.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_chat_response_details_ui_contract.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_chat_thinking_compact_layout.py Studio: drive UI font size through a typography scale instead of the root font size (#7359) 2026-07-23 01:26:56 -07:00
test_chat_title_generation.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_ci_shell_suite_coverage.py AMD: CI coverage for recent fixes, plus three wrong gfx ids (#7431) 2026-07-25 18:58:02 -05:00
test_cli_repo_variant.py Reduce and tighten comments and docstrings across the test suite (#6429) 2026-06-18 01:07:09 -07:00
test_cli_run_alias.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_cli_studio_defaults.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_cli_studio_stop_windows.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
test_compact_dropdown_submenus.py Studio: UI font size scales all text consistently without moving layout (#7355) 2026-07-23 00:44:42 -07:00
test_composer_rtl_bidi_attribute.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_deep_research_frontend_contract.py Studio: add Deep Research (#7219) 2026-07-26 23:36:02 -07:00
test_desktop_reliability_frontend_contract.py fix(studio): show chat sidebar menu on touch devices (#7297) 2026-07-23 19:11:05 -07:00
test_export_output_path_contract.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_frontend_dep_removal.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_generation_length_ui_contract.py Studio: prevent empty responses after model thinking (#7418) 2026-07-24 17:01:12 -07:00
test_gpu_inference_smoke.py Feat/model picker per model config v2 (#7207) 2026-07-20 22:53:22 -07:00
test_hardware_dispatch_matrix.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
test_hf_token_validation_tick_contract.py Studio: show HF token tick only after validation (#7268) 2026-07-21 03:53:11 -07:00
test_install_rollback_lifecycle.ps1 Installer: restore interrupted updates and clean stale rollback environments (#7342) 2026-07-23 01:29:53 -07:00
test_is_mlx_dispatch_gate.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_llama_cpp_wall_clock_cap.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_locale_root_direction_contract.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
test_mlx_training_worker_behaviors.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_model_picker_contracts.py Studio: keep automatic model loading toast visible until completion (#7425) 2026-07-28 00:16:28 -03:00
test_node_decision.ps1 Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
test_node_probe_guard.ps1 Studio: use an isolated Node.js for the frontend build instead of replacing the system Node/npm (#6533) 2026-06-21 21:17:29 -07:00
test_pdf_qa_recipe_contract.py Fix PDF-grounded QA recipe for QLoRA (#7107) 2026-07-26 20:19:53 +03:00
test_remote_connection_models_contract.py fix(studio): persist connection model selections for remote clients (#7298) 2026-07-23 19:11:50 -07:00
test_resolve_cuda_toolkit.ps1 Windows installer: fix DiskPart UAC mid-install, drive-root cache, and spurious unsloth.exe rename warning (#6296) 2026-06-22 03:09:08 -07:00
test_reveal_file_manager.py Feat/model picker per model config v2 (#7207) 2026-07-20 22:53:22 -07:00
test_settings_compact_overflow_contract.py Fix Settings layout overflow (#7167) 2026-07-16 03:12:09 -07:00
test_setup_pin_stale.ps1 install: let UNSLOTH_TORCH_INDEX_FAMILY / _URL override CUDA wheel detection (#6692) 2026-07-20 00:58:52 -07:00
test_stream_cancel_registration_timing.py Studio: queue local GGUF OpenAI-compatible requests before llama-server (#7047) 2026-07-10 17:05:48 -03:00
test_studio_gguf_export_script_pin.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_studio_text_descender_clipping.py tests: read checked-in files as UTF-8 instead of the platform default (#7438) 2026-07-26 23:31:56 -07:00
test_sync_allow_scripts_pins.py Studio: auto-sync allowScripts pins after dependency bumps (#6136) 2026-06-10 02:35:37 -07:00
test_torch_flavor.ps1 install: let UNSLOTH_TORCH_INDEX_FAMILY / _URL override CUDA wheel detection (#6692) 2026-07-20 00:58:52 -07:00
test_torch_index_pin_hardening.ps1 install: let UNSLOTH_TORCH_INDEX_FAMILY / _URL override CUDA wheel detection (#6692) 2026-07-20 00:58:52 -07:00
test_ui_font_scale_contract.py Studio UI font-scale test: normalise paths so the allowlists work on Windows (#7434) 2026-07-27 12:39:35 -05:00
test_uninstall_dual_install_icon.ps1 Windows installer: fix DiskPart UAC mid-install, drive-root cache, and spurious unsloth.exe rename warning (#6296) 2026-06-22 03:09:08 -07:00
test_usage_examples_agent_detection_contract.py feat: detect installed coding agent CLIs in Studio settings (#6909) 2026-07-08 05:26:50 -07:00
test_usage_examples_model_source_contract.py Studio: tighten the comments added by the OpenAI model-admission work (#7501) 2026-07-27 05:59:03 -07:00
test_voice_settings_select_width.py Studio: UI font size scales all text consistently without moving layout (#7355) 2026-07-23 00:44:42 -07:00
test_xpu_spoof_pipeline.py Add Intel XPU support to Unsloth Studio (#4724) 2026-07-24 02:22:07 -03:00