unsloth/scripts
Daniel Han cad00177a4
Stop the Studio temp probe leaving anything behind (#9298)
* Stop the temp probe leaving anything behind

Three pieces of residue an audit of #9178 turned up, all on hosts whose temp
is degraded, which is the population that change exists for.

Probing the host's own TMP no longer creates it. New-Item -Force builds the
whole parent chain, so a stale or mistyped TMP had the installer silently
materialize a tree at a path nobody chose and then trust it as temp. Absent
now reads as unusable, which is what the private fallback is for. A directory
the installer owns is still created, through an explicit switch.

A probe file that could not be deleted is reclaimed by the next run, once it
is a day old. The probe cannot clean up after itself when deletion is exactly
what failed, and nothing else knows the name, so each such run used to leave
one more file in the host's temp forever. The age gate keeps it away from a
probe running concurrently in another process.

A candidate root that fails its probe is taken back. The probe creates the
directory before it tests it, so an install that tried every root and gave up
left a "Unsloth Studio" tree on a machine Studio was never installed on. Only
if empty, so a directory that already held something is never touched.

* Keep the lock name in step with the runtime gate, and narrow the uninstall

Two corrections to #9178, both found by auditing it after it merged.

The final normalization kept the \\?\ prefix on a volume GUID. That string is
hashed into the runtime mutex name, and unsloth_cli/_studio_runtime_gate.py
strips \\?\ unconditionally, so on a host that resolves to a volume GUID the
installer and a running Studio computed different names for one directory and
neither excluded the other. Every extended spelling comes off again, as it did
before #9178. Rootedness does matter while a link target is being anchored,
and Resolve-StudioLinkTarget still keeps the extended form for that; nothing
after this point anchors anything.

The uninstaller resolved both LocalAppData spellings and gave each the full
data-directory delete. That delete is recursive and, alone among the deletes
in that script, requires no ownership sentinel and consults no deny list. The
two spellings differ mainly when one of them names a DIFFERENT USER's profile:
CreateProcessAsUser with a null environment block, runas /env, a service
token. The second spelling now gets only what install.ps1 puts there, ust-
directories matched by shape rather than prefix, and the temp directory and
its parent only if they are left empty. The port-file stop and the stop-roots
list go back to the single resolved root.

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

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

* Take back the parents the failed candidate created, not just the leaf

New-Item -Force builds the whole chain, so removing only the empty ust-*
directory still left "Unsloth Studio\temp" standing, which is the data
directory tree this cleanup exists to avoid conjuring on a machine Studio was
never installed on.

It now walks back up, but only through the directory names this path is made
of and only while each one is empty, so a tree that already held something is
left alone and ~\.unsloth, which is shared, is never removed. The test asserts
both halves: nothing remains under a root that was empty before, and a
pre-existing studio.port under one of the same parents survives.

* Sweep by shape, not by prefix, and say when the native resolver gives up

Two audits of the merged change independently reproduced the same deletion on
a HEALTHY host: the stale sweep matched ust-* by prefix, and a name whose tail
is not a parseable PID leaves $ownerPid at zero, which skips the liveness check
entirely. So a pre-existing directory such as "ust-legacy" or "ust-user-cache"
under LOCALAPPDATA\Unsloth Studio\temp, older than a day, was recursively
deleted by an ordinary install. The uninstaller added in the previous commit
already required the allocator's exact shape; the two had drifted apart. The
sweep now requires it too, case-insensitively, since Windows filenames are.

Separately, a native resolver that COMPILES and then throws falls back to the
lexical resolver, which is right, and marks the answer inexact, which is also
right, but said nothing. The degraded warning fires only when the compile
itself failed, so an operator on a host that looks perfectly healthy was left
with a silently inexact identity. It now warns once, with wording distinct
from the compile-failure warning. Exit codes and the fallback itself are
unchanged: aborting the install is the #9140 symptom, not the fix.

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

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

* An owner nobody recorded is unknown, not abandoned

Three findings from the second audit round, all in the ownership model.

The directory name carries the INSTALLER's pid. An installer killed between
Start-Process and the owner.pid write leaves a dead pid in the name while the
Studio it started is using that directory as its own %TEMP%, and a later run
read that as proof of abandonment and deleted it out from under the live
process. Reading owner.pid is proof; guessing from the name is not, so an
unrecorded owner now needs a week rather than a day before it is collected.
The pile still stays bounded, and a Studio actually using the directory
refreshes its timestamp long before that.

The uninstaller's private temp sweep ignored owner.pid entirely, so it removed
a directory a Studio from another install root, or another user, was live on.
It now leaves a live owner alone, exactly as install.ps1's sweep does.

That sweep also walked through a link: Get-ChildItem on a reparse point
enumerates the target, whose children carry no ReparsePoint attribute, so the
recursive delete took the target's tree. It now refuses a temp directory whose
own path or parent is a link. In install.ps1 the reparse branch moves ahead of
the owner logic, since the allocator never creates a link, reading owner.pid
out of one would read through it, and unlinking is safe whatever owns it.

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

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

* Unwind only what the probe created, and require an owner outside our profile

Two review items on this branch.

The cleanup walked back up through any empty, correctly named parent. A
pre-provisioned "Unsloth Studio\temp" with its own ACLs, or an empty
relocation junction, is configuration this installer did not create, and empty
plus correctly named is not the same as ours. Which ancestors were absent is
now recorded before the probe touches anything, and only those are unwound. A
reparse point stops the walk regardless.

The uninstaller's private temp sweep treated the directory-name shape as
ownership proof in every profile it looked at. Under the profile being
uninstalled that is right, since that is what is being removed. In the other
LocalAppData spelling, which can be a different user, a directory with no
recorded owner now stays: install.ps1 reads that state as unknown rather than
abandoned, because an installer killed before writing owner.pid leaves a live
Studio holding the directory.

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

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

* Reject a reparse point anywhere above another profile's temp root

A junction does not have to sit next to the temp directory to redirect it.
LocalAppData, the profile directory or the drive root can be the reparse
point, and then "<root>\Unsloth Studio\temp" and its parent both look
like ordinary directories while Get-ChildItem lands somewhere else. For a
LocalAppData spelling that is not the profile being uninstalled, that
somewhere else can be another user's tree.

Walk every ancestor up to the root for the alternate spelling. The
profile the uninstall is actually for keeps the two-level check: a
redirected LocalAppData there is the same user's own storage, and
refusing would leave the installer's own temp tree behind on every host
that uses folder redirection.

* Sweep the private temp trees before removing the data directory

The primary private temp directory lives inside the data directory, and
the data directory is removed wholesale. A Studio from another install
root can still be alive on that temp directory as its %TEMP%, so the
live-owner check never got the chance to protect it.

Run the sweep first and have it hand back everything it kept, then let
the data-dir removal delete around those paths through _RemoveTreeKeeping.
With an empty preserve list that is _RemovePath and nothing else, which
is what every ordinary uninstall gets.

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

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

* Sweep only the probe names the installer generates

The stale-probe cleanup runs in the HOST's temp directory, where a file
that merely starts with unsloth-probe- and ends in .tmp belongs to
somebody else. Match the shape the probe actually writes, eight hex
characters, the same rule the private temp sweep already uses.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-20 05:48:48 -07:00
..
data Acknowledge the Colab rotation the oracle snapshot is measuring (#9376) 2026-08-20 04:15:05 -07:00
build_prequant_checkpoint.py Studio: run MiniMax-H3's INT8 denoiser from the ConvRot checkpoint (#8293) 2026-08-10 01:00:16 -07:00
build_te_prequant_checkpoint.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
build_whisper_cpp.sh Studio: add local speech-to-text dictation engine (#7095) 2026-07-23 01:39:03 -07:00
check_frontend_dep_removal.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
check_new_install_scripts.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
compare_engines.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
compile_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
diffusion_bench.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
diffusion_quality.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
enforce_kwargs_spacing.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
fbcache_flux_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
fp8_overflow_check.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
image_speedmem_bench.py Report the precision actually in use, and refuse an explicit one we cannot honor (#8165) 2026-08-09 01:15:13 -07:00
install_gemma4_mlx.sh Update Install Scripts (#5968) 2026-06-03 05:39:42 -07:00
install_qwen3_6_mlx.sh Studio: drop the mlx-lm 0.31.3 exclusion so current mlx-vlm resolves (#7061) 2026-08-07 02:41:04 -03:00
install_rocm_wsl_strixhalo.sh Pin the ROCm-on-WSL bootstrap to immutable refs (#8540) 2026-08-12 06:37:05 -07:00
int8_linear_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
leverage_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
lint_backend_python_floor.py One interpreter leg on a pull request, and a floor lint that reads more than syntax (#9100) 2026-08-17 18:48:26 -07:00
lint_no_parallel_clamp.py Studio: stop MTP forcing llama-server to a single parallel slot (#8172) 2026-08-08 21:09:58 -07:00
lint_workflow_triggers.py Harden the workflow-trigger lint: scan .yaml, and host it outside the workflow it audits (#8545) 2026-08-12 09:55:26 -07:00
lockfile_supply_chain_audit.py security: lockfile audit must block non-registry sources and missing integrity by default (#8541) 2026-08-12 05:36:17 -07:00
make_dmg_background.py Start the bottom taper under the icon so the disc still reads round (#8321) 2026-08-10 01:06:59 -07:00
notebook_to_python.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
notebook_validator.py Make the Colab oracle tripwire able to fire, and stop blaming start.py for a hung agent CLI (#7838) 2026-08-04 07:42:42 -07:00
nvfp4_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
nvfp4_t211_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
online_tokenization_ab.py Studio: tokenize the dataset online for plain-text single-pass runs (#8960) 2026-08-16 04:55:53 -07:00
perf_levers_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
perf_verify.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
prequant_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
profile_startup.py Measure where Studio's startup time actually goes (#7553) 2026-07-28 22:24:34 -07:00
quant_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
run_ruff_format.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
scan_npm_packages.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
scan_npm_packages_baseline.json scan_packages: key baseline on matched-code hash so payloads in baselined files are not auto-suppressed (#6552) 2026-07-01 04:03:59 -07:00
scan_packages.py Scan package archives across cores instead of one at a time (#9024) 2026-08-16 21:46:14 -07:00
scan_packages_baseline.json Allowlist huggingface-hub's http_backoff loop so Security audit stops failing (#9252) 2026-08-18 22:02:31 -07:00
sd_cpp_smoke.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
sdpa_mask_backend_probe.py Diffusion: replace the attention perf claims with re-measured numbers (#8021) 2026-08-06 05:47:31 -07:00
sparse_accum_probe.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
stamp_studio_release.py Replace standalone Studio wording with Unsloth (#7221) 2026-07-19 00:47:04 -07:00
sync_allow_scripts_pins.py Studio: auto-sync allowScripts pins after dependency bumps (#6136) 2026-06-10 02:35:37 -07:00
uninstall.ps1 Stop the Studio temp probe leaving anything behind (#9298) 2026-08-20 05:48:48 -07:00
uninstall.sh Studio: install sd.cpp under the Studio home, not beside it (#8226) 2026-08-10 07:03:20 -07:00
verify_comment_only_diff.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
verify_import_hoist.py feat(studio): rework train page setup flow (#7633) 2026-08-06 04:26:05 -07:00
verify_prequant_backend.py Studio: add image generation, editing workflows and LoRA training with Unsloth GGUFs (#6763) 2026-08-04 08:11:01 -07:00
video_quality.py Video: make MiniMax-H3's Diffusers path fast by default (#8320) 2026-08-10 04:31:09 -07:00
virustotal_scan.py Reduce antivirus false positives in the desktop installers (#8586) 2026-08-13 07:02:18 -07:00