comments: condense multiline blocks added by this PR

Across the 27-cycle review process, comments accumulated as multiline
blocks explaining each fix's history (cycle numbers, prior bugs,
reviewer rationale). Compress every block to 1-2 lines that capture
just the WHY, dropping cycle references and history that belongs in
the PR description / commit log instead.

Net: 268 deletions / 124 insertions (-144 lines) of comments only.
Behavior unchanged. Verified: bash -n, pwsh parser, python ast.parse,
cargo check all pass.
This commit is contained in:
Daniel Han 2026-04-27 00:08:07 +00:00
commit b7fa2c8718
15 changed files with 124 additions and 268 deletions

View file

@ -70,10 +70,8 @@ function Install-UnslothStudio {
# Resolve install destinations. Priority: env vars, then USERPROFILE-redirect, then default.
$envOverride = if ($env:UNSLOTH_STUDIO_HOME) { $env:UNSLOTH_STUDIO_HOME } elseif ($env:STUDIO_HOME) { $env:STUDIO_HOME } else { $null }
# Custom Studio roots are not supported in Tauri-mode installs because
# the desktop app still resolves the legacy %USERPROFILE%\.unsloth\studio
# path. Pass through when the override resolves to that legacy default
# (the desktop app already uses it); fail fast otherwise.
# Custom Studio roots are not supported with --tauri (desktop app still
# resolves %USERPROFILE%\.unsloth\studio). Pass through if override == legacy.
if ($TauriMode -and $envOverride) {
$_tauriOverride = $envOverride
if ($_tauriOverride -eq "~" -or $_tauriOverride -like "~/*" -or $_tauriOverride -like "~\*") {
@ -86,8 +84,7 @@ function Install-UnslothStudio {
try {
$_legacyTauriRoot = [System.IO.Path]::GetFullPath($_legacyTauriRoot)
} catch {}
# Strip trailing separators so a legacy override with a trailing
# backslash or slash still matches the legacy root.
# Strip trailing separators so ".../studio\" matches ".../studio".
$_trimSeps = @(
[System.IO.Path]::DirectorySeparatorChar,
[System.IO.Path]::AltDirectorySeparatorChar
@ -106,35 +103,29 @@ function Install-UnslothStudio {
$defaultProfile = $null
try { $defaultProfile = [Environment]::GetFolderPath("UserProfile") } catch {}
# Default DataDir uses LOCALAPPDATA. Guard the lookup: in service / CI
# contexts LOCALAPPDATA may be unset, and Join-Path under
# $ErrorActionPreference='Stop' would otherwise abort the installer.
# LOCALAPPDATA may be unset in service / CI contexts; Join-Path would abort
# under ErrorActionPreference=Stop without this guard.
$defaultDataDir = if ($env:LOCALAPPDATA -and -not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
Join-Path $env:LOCALAPPDATA "Unsloth Studio"
} else { $null }
if ($envOverride) {
# Expand a leading '~' to $HOME / $env:USERPROFILE because env-var
# values are not subject to tilde expansion in any shell.
# Tilde expansion: env vars aren't subject to it when quoted on assignment.
if ($envOverride -eq "~" -or $envOverride -like "~/*" -or $envOverride -like "~\*") {
$envOverride = (Join-Path $env:USERPROFILE $envOverride.Substring(1).TrimStart('/','\'))
}
try {
# New-Item has no -LiteralPath in PowerShell 5.1 and -Path treats
# square brackets as wildcards. Use the .NET API so a custom root
# like C:\workspaces\studio[abc] is handled literally.
# .NET API: New-Item -Path treats brackets as wildcards and has no
# -LiteralPath in PS 5.1, so a root like C:\studio[abc] would fail.
[System.IO.Directory]::CreateDirectory($envOverride) | Out-Null
$StudioHome = (Resolve-Path -LiteralPath $envOverride).Path
} catch {
Write-Host "ERROR: STUDIO_HOME=$envOverride cannot be created or accessed." -ForegroundColor Red
exit 1
}
# Default ToString() form already produces a unique GUID string.
$probe = Join-Path $StudioHome (".unsloth-write-probe-" + [guid]::NewGuid())
try {
# WriteAllText is literal-path safe and closes the file handle
# before the Remove-Item below; New-Item -Path would fail on
# bracketed roots (wildcard expansion) just like the dir case.
# WriteAllText: literal-path safe + closes handle so Remove-Item works.
[System.IO.File]::WriteAllText($probe, "")
Remove-Item -LiteralPath $probe -Force -ErrorAction SilentlyContinue
} catch {
@ -415,8 +406,7 @@ function Install-UnslothStudio {
# This prevents runtime variable expansion for paths containing '$'.
$SingleQuotedExePath = $UnslothExePath -replace "'", "''"
# $StudioDataDir was resolved at install start (default = LOCALAPPDATA\Unsloth Studio,
# or $StudioHome\share when UNSLOTH_STUDIO_HOME / STUDIO_HOME is set).
# $StudioDataDir = LOCALAPPDATA\Unsloth Studio, or $StudioHome\share in env-mode.
if (-not $StudioDataDir -or [string]::IsNullOrWhiteSpace($StudioDataDir)) {
substep "DataDir path unavailable; skipped shortcut creation" "Yellow"
return
@ -457,18 +447,14 @@ function Install-UnslothStudio {
[System.IO.Directory]::CreateDirectory($appDir) | Out-Null
}
# Persist UNSLOTH_STUDIO_HOME inside the generated launcher when
# in env-override mode. Lets fresh shells launch Studio without
# the user re-exporting the env var. Default installs get an
# empty string here so behavior matches today exactly.
# Env-mode: persist UNSLOTH_STUDIO_HOME (and llama path) in the
# launcher so fresh shells don't need to re-export. Default installs
# get an empty prefix so behavior matches pre-PR exactly.
$studioHomeExport = if ($StudioRedirectMode -eq 'env') {
# Mirror setup.ps1: when an env override happens to equal the
# legacy default, llama.cpp still lives at ~/.unsloth/llama.cpp.
# Keep the persisted UNSLOTH_LLAMA_CPP_PATH consistent with that.
# When override == legacy default, llama.cpp stays at
# ~/.unsloth/llama.cpp (one shared build). Canonicalize the
# legacy side so the comparison survives path normalization.
$_legacyStudio = Join-Path $env:USERPROFILE ".unsloth\studio"
# Canonicalize the legacy side (when it exists) to match the
# resolved $StudioHome from the env-override path. This keeps
# the legacy-equality check stable across path normalization.
if (Test-Path -LiteralPath $_legacyStudio -PathType Container) {
$_legacyStudio = (Resolve-Path -LiteralPath $_legacyStudio).Path
}
@ -479,9 +465,7 @@ function Install-UnslothStudio {
}
$_sq = $StudioHome -replace "'", "''"
$_llama = $_llamaPath -replace "'", "''"
# UNSLOTH_LLAMA_CPP_PATH is a pre-existing user-controlled
# llama.cpp directory override. Only default it when the
# caller has not already set one in their environment.
# UNSLOTH_LLAMA_CPP_PATH is a pre-existing user override; only default if unset.
"`$env:UNSLOTH_STUDIO_HOME = '$_sq'`nif (-not `$env:UNSLOTH_LLAMA_CPP_PATH) {`n `$env:UNSLOTH_LLAMA_CPP_PATH = '$_llama'`n}`n"
} else { "" }
@ -707,10 +691,8 @@ shell.Run cmd, 0, False
}
}
# Env-override installs are workspace-scoped: skip persistent
# Desktop / Start Menu .lnk shortcuts that would point at a
# path the user may later delete. The launcher scripts and
# icon written above ARE kept regardless of mode.
# Env-mode: skip persistent Desktop / Start Menu .lnk shortcuts
# that may point at a deleted workspace; launcher + icon stay.
if ($StudioRedirectMode -eq 'env') {
substep "wrote launcher at $launcherPs1 (persistent shortcuts skipped in env-override mode)"
return
@ -909,8 +891,7 @@ shell.Run cmd, 0, False
# a version string back to a conda interpreter.
Write-TauriLog "STEP" "Creating virtual environment"
if (-not (Test-Path -LiteralPath $StudioHome)) {
# New-Item has no -LiteralPath parameter; use the .NET API to honor
# bracket characters in custom Studio roots.
# .NET API: New-Item -Path treats brackets as wildcards.
[System.IO.Directory]::CreateDirectory($StudioHome) | Out-Null
}
@ -946,11 +927,9 @@ shell.Run cmd, 0, False
$StudioRedirectMode -ne 'env' `
-and (Test-Path -LiteralPath (Join-Path $env:USERPROFILE "unsloth_studio\Scripts\python.exe"))
) {
# CWD-relative venv from old install.ps1 -- migrate to absolute path.
# Skip in env-override mode: workspace-scoped installs must not
# move the user's pre-existing default-install venv away from
# %USERPROFILE%, which would break their legacy default install
# and contaminate the workspace root.
# CWD-relative venv from old install.ps1 -> migrate to absolute path.
# Skip in env-mode so we don't relocate the default-install venv into
# the workspace root.
$CwdVenv = Join-Path $env:USERPROFILE "unsloth_studio"
substep "found CWD-relative Studio environment, migrating to $VenvDir..."
Move-Item -LiteralPath $CwdVenv -Destination $VenvDir -Force
@ -1225,9 +1204,8 @@ shell.Run cmd, 0, False
# Use 'studio setup' (not 'studio update') because 'update' pops
# SKIP_STUDIO_BASE, which would cause redundant package reinstallation
# and bypass the fast-path version check from PR #4667.
# Only propagate UNSLOTH_STUDIO_HOME for actual env-override installs;
# otherwise default-install setups would mistakenly take the env path
# (placing llama.cpp under $StudioHome\llama.cpp instead of legacy).
# Propagate UNSLOTH_STUDIO_HOME only for env-override installs; otherwise
# an inherited value would put llama.cpp in the wrong place.
$previousUnslothStudioHome = $env:UNSLOTH_STUDIO_HOME
$hadPreviousUnslothStudioHome = ($null -ne $previousUnslothStudioHome)
if ($StudioRedirectMode -eq 'env') {
@ -1314,10 +1292,8 @@ shell.Run cmd, 0, False
Write-Host " Launch unsloth studio directly via '$UnslothExe' until the next successful install." -ForegroundColor Yellow
}
}
# Only add to PATH when the launcher actually exists on disk.
# Skip persistent registry PATH modification when env-override is active:
# the workspace path may be deleted, and we should not pollute the user's
# persistent PATH with it. Caller is expected to add $StudioHome\bin manually.
# Add to PATH only when launcher exists. Env-mode: session-only export,
# no registry change (workspace path may be deleted later).
$pathAdded = $false
if (Test-Path -LiteralPath $ShimExe) {
if ($StudioRedirectMode -eq 'env') {
@ -1332,9 +1308,8 @@ shell.Run cmd, 0, False
}
Refresh-SessionPath # sync current session with registry
# Re-prepend the env-override shim AFTER Refresh-SessionPath, otherwise
# a previously-installed legacy User PATH entry would win precedence
# (Refresh rebuilds Path as Machine > User > current $env:Path).
# Re-prepend env-mode shim AFTER Refresh-SessionPath; otherwise a legacy
# User PATH entry (Machine > User > current $env:Path) would win.
if ($StudioRedirectMode -eq 'env' -and (Test-Path -LiteralPath $ShimExe)) {
$env:Path = "$ShimDir;$env:Path"
}
@ -1345,9 +1320,7 @@ shell.Run cmd, 0, False
return
}
# New-StudioShortcuts itself gates the persistent Desktop / Start
# Menu .lnk shortcuts based on $StudioRedirectMode; the launcher
# script and icon are always written so env-mode shims still resolve.
# New-StudioShortcuts gates the .lnk shortcuts on env-mode internally.
New-StudioShortcuts -UnslothExePath $UnslothExe
# Launch studio automatically in interactive terminals;
@ -1358,9 +1331,7 @@ shell.Run cmd, 0, False
} else {
step "launch" "manual commands:"
if ($StudioRedirectMode -eq 'env') {
# Env-override mode skips persistent registry PATH update, so
# `unsloth` may not resolve in a fresh shell. Print the
# absolute shim path so callers can launch directly.
# Env-mode skips registry PATH; print the absolute shim path.
$_shim = Join-Path $StudioHome "bin\unsloth.exe"
substep "& `"$_shim`" studio -H 0.0.0.0 -p 8888"
substep "or activate env first:"

View file

@ -71,13 +71,8 @@ if [ "$_VERBOSE" = true ]; then
export UNSLOTH_VERBOSE=1
fi
# Custom Studio roots are not supported in Tauri-mode installs because the
# desktop app still resolves the legacy ~/.unsloth/studio path. Producing
# a custom-root --tauri install would yield a desktop app that cannot
# locate the freshly installed binary or auth secret. Fail fast for real
# custom roots; pass through when the override resolves to the legacy
# default ($HOME/.unsloth/studio), since that is exactly what the desktop
# app already uses.
# Custom Studio roots are not supported with --tauri (desktop app still
# resolves ~/.unsloth/studio). Pass through if the override == legacy default.
if [ "$TAURI_MODE" = true ]; then
_tauri_override="${UNSLOTH_STUDIO_HOME:-${STUDIO_HOME:-}}"
if [ -n "$_tauri_override" ]; then
@ -85,25 +80,20 @@ if [ "$TAURI_MODE" = true ]; then
"~") _tauri_override="$HOME" ;;
"~/"*) _tauri_override="$HOME/${_tauri_override#'~/'}" ;;
esac
# Resolve symlinks/relative paths if the dir exists, else use as-is.
# CDPATH= prevents `cd` from echoing the resolved path on stdout when
# the user has CDPATH set in their environment. -P resolves symlinks.
# Canonicalize both sides (CDPATH=, -P) so a CDPATH-set env or
# symlinked $HOME doesn't break the legacy-equality comparison.
if [ -d "$_tauri_override" ]; then
_tauri_override_abs=$(CDPATH= cd -P -- "$_tauri_override" 2>/dev/null && pwd -P) \
|| _tauri_override_abs="$_tauri_override"
else
_tauri_override_abs="$_tauri_override"
fi
# Strip trailing separators so a legacy override with a trailing
# slash (".../studio/") still matches the legacy root (".../studio").
# Strip trailing separators so ".../studio/" matches ".../studio".
while [ "$_tauri_override_abs" != "/" ] \
&& [ "${_tauri_override_abs%/}" != "$_tauri_override_abs" ]; do
_tauri_override_abs=${_tauri_override_abs%/}
done
_tauri_legacy_root="$HOME/.unsloth/studio"
# Apply the same physical-path canonicalization to the legacy root
# so a symlinked $HOME (e.g. /home/alice -> /u/alice) doesn't make
# the comparison fail when both sides point at the same directory.
if [ -d "$_tauri_legacy_root" ]; then
_tauri_legacy_root=$(CDPATH= cd -P -- "$_tauri_legacy_root" 2>/dev/null && pwd -P) \
|| _tauri_legacy_root="$HOME/.unsloth/studio"
@ -220,14 +210,11 @@ tauri_log() {
PYTHON_VERSION="" # resolved after platform detection
# Resolve install destinations. Priority: env vars, then HOME-redirect, then default.
# Best-effort HOME-redirect detection: skipped on hosts without getent/dscl.
# Resolve install destinations: env override, HOME-redirect (best-effort
# via getent/dscl), or default.
_resolve_studio_destinations() {
_override="${UNSLOTH_STUDIO_HOME:-${STUDIO_HOME:-}}"
# Expand leading ~ / ~/path to $HOME because env vars are not subject to
# tilde expansion when set with quotes around the value. Quote the
# ~/ inside the prefix-removal so the shell does not tilde-expand it
# back to $HOME/ before matching.
# Tilde expansion: env vars are not subject to it when quoted on assignment.
case "$_override" in
"~") _override="$HOME" ;;
"~/"*) _override="$HOME/${_override#'~/'}" ;;
@ -235,8 +222,6 @@ _resolve_studio_destinations() {
if [ -n "$_override" ]; then
mkdir -p -- "$_override" 2>/dev/null || { echo "ERROR: STUDIO_HOME=$_override cannot be created." >&2; exit 1; }
[ -w "$_override" ] || { echo "ERROR: STUDIO_HOME=$_override is not writable." >&2; exit 1; }
# CDPATH= prevents `cd` from echoing a CDPATH-prefixed path on stdout
# if the user has CDPATH set; -P / pwd -P canonicalizes symlinks.
STUDIO_HOME="$(CDPATH= cd -P -- "$_override" && pwd -P)" || exit 1
DATA_DIR="$STUDIO_HOME/share"
_LOCAL_BIN="$STUDIO_HOME/bin"
@ -381,10 +366,8 @@ create_studio_shortcuts() {
mkdir -p "$_css_data_dir"
# ── Write launcher script ──
# Heredoc is single-quoted (no $-expansion at write time). The
# @@DATA_DIR@@ placeholder below is substituted via sed after the
# heredoc is written so the runtime launcher reads studio.conf
# from the same DATA_DIR install.sh resolved.
# Single-quoted heredoc; @@DATA_DIR@@ is substituted via sed below so
# the runtime launcher reads studio.conf from the resolved DATA_DIR.
cat > "$_css_launcher" << 'LAUNCHER_EOF'
#!/usr/bin/env bash
# Unsloth Studio Launcher
@ -638,33 +621,20 @@ else
fi
LAUNCHER_EOF
# Substitute @@DATA_DIR@@. Default and HOME-redirect installs keep the
# legacy runtime form (DATA_DIR="$HOME/.local/share/unsloth") so a later
# shell with a different $HOME still resolves DATA_DIR correctly --
# byte-identical to pre-PR. Only env-mode installs bake the resolved
# absolute path because their root is fixed at install time.
# Portable in-place edit: redirect to tempfile, then mv. Avoids the
# GNU-vs-BSD-vs-BusyBox `sed -i` divergence (per Gemini review feedback).
# Default / HOME-redirect installs replace the placeholder line with the
# legacy literal (DATA_DIR="$HOME/.local/share/unsloth") so a later shell
# with a different $HOME still resolves correctly -- byte-identical to
# pre-PR. Env-mode installs bake the resolved absolute path because their
# root is fixed at install time.
if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then
# Two-stage escape so path metacharacters survive sed and shell
# single-quoted embedding. Verified end-to-end with apostrophes,
# spaces, &, |, $; do not "simplify" to fewer/more backslashes.
# 1) ' -> '\'' for safe single-quote shell embedding.
# The shell pattern "s/'/'\\''/g" passes "s/'/'\''/g" to sed
# (\\ -> \). sed's replacement '\'' contains a literal
# backslash-quote-quote, producing the canonical close-quote /
# escaped-quote / open-quote sequence on output. Result:
# `a b's` -> `a b'\''s`.
# 2) For the value to land inside a sed s|...|VALUE|g call below,
# escape \, &, and | (the chosen delimiter) so they survive sed
# replacement. Result of stage 2: `a b'\''s` -> `a b'\\''s`.
# Two-stage escape: (1) `'` -> `'\''` for shell single-quote embedding,
# (2) backslash/&/| escape so the value survives the s|...|VALUE| sed
# below. Verified end-to-end with apostrophes, spaces, &, |, $.
_sq_escaped=$(printf '%s' "$DATA_DIR" | sed "s/'/'\\\\''/g")
_sed_safe=$(printf '%s' "$_sq_escaped" | sed 's/[\\&|]/\\&/g')
sed "s|@@DATA_DIR@@|$_sed_safe|g" "$_css_launcher" > "$_css_launcher.tmp" \
&& mv "$_css_launcher.tmp" "$_css_launcher"
else
# Replace the placeholder line entirely with the legacy literal
# so the launcher reads $HOME at runtime, not install time.
sed "s|DATA_DIR='@@DATA_DIR@@'|DATA_DIR=\"\$HOME/.local/share/unsloth\"|" \
"$_css_launcher" > "$_css_launcher.tmp" \
&& mv "$_css_launcher.tmp" "$_css_launcher"
@ -672,30 +642,16 @@ LAUNCHER_EOF
chmod +x "$_css_launcher"
# Write the exe path to a separate conf file sourced by the launcher.
# Single-quote wrapping with the standard `'\''` escape (shell pattern
# "s/'/'\\''/g" -> sed "s/'/'\''/g"; replacement `'\''` produces
# close-quote, backslash-quote, open-quote on output). No sed templating
# is applied to studio.conf, so no second-stage backslash/&/| escaping
# is needed here.
# studio.conf: exe path + (env-mode only) persisted env vars so fresh
# shells launch the right install without re-exporting.
_css_quoted_exe=$(printf '%s' "$_css_exe" | sed "s/'/'\\\\''/g")
{
printf '%s\n' "UNSLOTH_EXE='$_css_quoted_exe'"
# Persist UNSLOTH_STUDIO_HOME for env-override installs so the launcher,
# CLI, and backend pick up the same root in fresh shells where the user
# did not re-export the env var. Also persist UNSLOTH_LLAMA_CPP_PATH so
# unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR binding finds the
# custom-root build. Default installs do NOT get these lines so the
# legacy ~/.unsloth/studio + ~/.unsloth/llama.cpp resolution stands.
if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then
# Mirror setup.sh's legacy-equality check: when an env override
# happens to point at the legacy default, llama.cpp still lives
# at ~/.unsloth/llama.cpp (one shared build across legacy
# installs) -- keep UNSLOTH_LLAMA_CPP_PATH consistent with that.
# $STUDIO_HOME is canonicalized (cycle 24) but $HOME/.unsloth/studio
# is still logical. Canonicalize the legacy side too so a symlinked
# $HOME doesn't make the comparison fail when both point at the
# same dir.
# When an override resolves to the legacy default, llama.cpp
# still lives at ~/.unsloth/llama.cpp (one shared build).
# Canonicalize the legacy side so a symlinked $HOME doesn't
# break the comparison.
_css_legacy_studio="$HOME/.unsloth/studio"
if [ -d "$_css_legacy_studio" ]; then
_css_legacy_studio=$(CDPATH= cd -P -- "$_css_legacy_studio" 2>/dev/null && pwd -P) \
@ -709,9 +665,8 @@ LAUNCHER_EOF
_css_quoted_home=$(printf '%s' "$STUDIO_HOME" | sed "s/'/'\\\\''/g")
_css_quoted_llama=$(printf '%s' "$_css_llama_path" | sed "s/'/'\\\\''/g")
printf '%s\n' "export UNSLOTH_STUDIO_HOME='$_css_quoted_home'"
# UNSLOTH_LLAMA_CPP_PATH is a pre-existing custom-llama.cpp-dir
# override the Python backend / unsloth-zoo respect. Only default
# it when the caller has not already set one in their shell.
# UNSLOTH_LLAMA_CPP_PATH is a pre-existing user-controlled
# llama.cpp dir override; only default it if unset.
printf '%s\n' 'if [ -z "${UNSLOTH_LLAMA_CPP_PATH:-}" ]; then'
printf '%s\n' " export UNSLOTH_LLAMA_CPP_PATH='$_css_quoted_llama'"
printf '%s\n' 'fi'
@ -762,11 +717,9 @@ LAUNCHER_EOF
fi
# ── Platform-specific shortcuts ──
# Env-override installs are workspace-scoped: skip persistent
# desktop / Start-Menu / dock launchers that would point at a path
# the user may later delete. The runtime launcher + studio.conf +
# icon written above are still essential for the env-mode shim and
# ARE kept regardless of mode.
# Env-mode installs are workspace-scoped: skip persistent desktop /
# Start-Menu / dock launchers that may point at a deleted workspace.
# Runtime launcher + studio.conf + icon are still written above.
if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then
substep "wrote launcher at $_css_launcher (persistent shortcuts skipped in env-override mode)"
return 0
@ -849,12 +802,9 @@ DESKTOP_EOF
</plist>
PLIST_EOF
# Executable stub
# The stub embeds the resolved launcher path. Use a single-quoted
# heredoc + post-write sed substitution + single-quoted shell
# embedding so any path metacharacters ($, ", `, &, |, \, ') in
# $_css_data_dir do not expand at .app launch time. Same pattern
# as the launch-studio.sh @@DATA_DIR@@ substitution above.
# Executable stub: same single-quoted-heredoc + sed-substitute
# pattern as launch-studio.sh so $-vars in $_css_data_dir don't
# expand at .app launch time.
_css_sq_dir=$(printf '%s' "$_css_data_dir" | sed "s/'/'\\\\''/g")
_css_sed_dir=$(printf '%s' "$_css_sq_dir" | sed 's/[\\&|]/\\&/g')
cat > "$_css_macos_dir/launch-studio" << 'STUB_EOF'
@ -1807,10 +1757,8 @@ _SKIP_FRONTEND=0
if [ "$TAURI_MODE" = true ]; then
_SKIP_FRONTEND=1
fi
# Helper: prepend UNSLOTH_STUDIO_HOME=$STUDIO_HOME to "$@" only for actual
# env-override installs. Avoids word-splitting on whitespace paths -- a
# string-form '_STUDIO_ENV_FOR_SETUP="UNSLOTH_STUDIO_HOME=$STUDIO_HOME"'
# would split '/tmp/space path' into separate argv entries.
# Prepend UNSLOTH_STUDIO_HOME=$STUDIO_HOME to "$@" for env-override installs
# without word-splitting on whitespace paths.
_run_setup_with_studio_home() {
if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then
UNSLOTH_STUDIO_HOME="$STUDIO_HOME" "$@"
@ -1844,9 +1792,8 @@ else
fi
# ── Make 'unsloth' available via $_LOCAL_BIN (resolved earlier) ──
# When STUDIO_HOME / UNSLOTH_STUDIO_HOME is used, $_LOCAL_BIN is workspace-scoped
# ($STUDIO_HOME/bin) and we skip the shell-rc PATH append so we do not pollute
# the user's profile with a path that may be deleted with the workspace.
# Env-mode: $_LOCAL_BIN is $STUDIO_HOME/bin; skip shell-rc PATH append so we
# don't pollute the user's profile with a workspace-scoped path.
mkdir -p "$_LOCAL_BIN"
ln -sf "$VENV_DIR/bin/unsloth" "$_LOCAL_BIN/unsloth"
@ -1879,9 +1826,8 @@ case ":$PATH:" in
esac
# Non-Tauri installs keep shortcuts even if setup reports failure.
# create_studio_shortcuts itself gates the persistent desktop/menu
# launchers based on $_STUDIO_HOME_REDIRECT; the runtime launcher +
# studio.conf + icon are always written so env-mode shims still resolve.
# create_studio_shortcuts gates persistent menu shortcuts on env-mode;
# launcher + studio.conf + icon are always written.
if [ "$TAURI_MODE" != true ]; then
create_studio_shortcuts "$VENV_ABS_BIN/unsloth" "$OS"
fi
@ -1925,15 +1871,11 @@ if [ -t 1 ]; then
exit "$_LAUNCH_EXIT"
else
step "launch" "manual commands:"
# Single-quote-escape paths so values with spaces / apostrophes /
# metacharacters round-trip through copy-paste from a non-TTY install log.
# Single-quote-escape so paths with spaces / apostrophes copy-paste cleanly.
_li_shim_q="'$(printf '%s' "${_LOCAL_BIN}/unsloth" | sed "s/'/'\\\\''/g")'"
_li_act_q="'$(printf '%s' "${VENV_DIR}/bin/activate" | sed "s/'/'\\\\''/g")'"
if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then
# In env-override mode we deliberately skip the persistent shell
# rc PATH append, so a fresh shell will not have `unsloth` on PATH
# unless the caller re-exports UNSLOTH_STUDIO_HOME. Print the
# absolute shim path and the activate-then-run alternative.
# Env-mode skips the rc PATH append, so print the absolute shim path.
substep "$_li_shim_q studio -H 0.0.0.0 -p 8888"
substep "or activate env first:"
substep "source $_li_act_q"

View file

@ -491,14 +491,10 @@ class LlamaCppBackend:
if win_bin.is_file():
return str(win_bin)
# 2-4. Mirror setup.sh / setup.ps1's install layout:
# - In env-override mode (custom Studio root != legacy default),
# llama.cpp is installed under $STUDIO_HOME/llama.cpp.
# - Otherwise (default install or HOME redirect), llama.cpp is
# installed at ~/.unsloth/llama.cpp (sibling of the Studio dir).
# Default-mode searches must NOT look at $STUDIO_HOME/llama.cpp,
# to avoid picking a stale partial install from a previous failed
# install over the real legacy binary.
# 2-4. Match installer layout: env-mode -> $STUDIO_HOME/llama.cpp;
# default/HOME-redirect -> ~/.unsloth/llama.cpp (sibling of studio).
# Default mode skips $STUDIO_HOME/llama.cpp so a stale partial install
# there cannot shadow the real legacy binary.
legacy_llama = Path.home() / ".unsloth" / "llama.cpp"
try:
from utils.paths.storage_roots import studio_root as _sr # noqa: WPS433
@ -511,7 +507,6 @@ class LlamaCppBackend:
search_roots = [_resolved_sr / "llama.cpp", legacy_llama]
except (ImportError, OSError, ValueError):
search_roots = [legacy_llama]
# De-dupe while preserving order (defensive in case both resolve equal).
_seen: set[str] = set()
for unsloth_home in [
r for r in search_roots if str(r) not in _seen and not _seen.add(str(r))
@ -2084,12 +2079,9 @@ class LlamaCppBackend:
# (binary must be *under* one of these)
install_roots: list[Path] = []
# Resolved Studio root (covers env-override custom installs).
# Mirror _find_llama_server_binary: only treat $STUDIO_HOME/llama.cpp
# as Studio-owned when STUDIO_HOME is a real env override, not the
# legacy default. In default-mode installs llama.cpp is a sibling
# of studio (~/.unsloth/llama.cpp), and ~/.unsloth/studio/llama.cpp
# may be owned by a different tool or a stale partial install.
# Env-mode custom root (mirrors _find_llama_server_binary). Default
# mode excluded so we don't kill llama-server from another tool
# running under ~/.unsloth/studio/llama.cpp.
try:
from utils.paths.storage_roots import studio_root as _sr # noqa: WPS433

View file

@ -23,12 +23,9 @@ if _backend_dir not in sys.path:
# See: https://github.com/python/cpython/issues/102396
import _platform_compat # noqa: F401
# Custom-root env propagation BEFORE any unsloth/unsloth-zoo import: when
# main.py is launched directly via `uvicorn main:app` from a custom-root
# venv, neither unsloth_cli nor run.py runs, so UNSLOTH_LLAMA_CPP_PATH
# stays unset and unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR binding
# falls back to the legacy ~/.unsloth/llama.cpp. Mirror run.py's logic:
# only export when the resolved root is a real custom override.
# Direct `uvicorn main:app` launches bypass run.py, so re-export here too
# (mirrors run.py). Required BEFORE the unsloth-zoo import below, since
# its LLAMA_CPP_DEFAULT_DIR binding is import-time.
from utils.paths.storage_roots import studio_root as _studio_root
_LEGACY_STUDIO_ROOT = (_Path.home() / ".unsloth" / "studio").resolve()

View file

@ -159,17 +159,14 @@ def _find_free_port(host: str, start: int, max_attempts: int = 20) -> int:
)
# Resolved via the shared helper so custom installs land in the right place.
from utils.paths.storage_roots import studio_root as _studio_root
_PID_FILE = _studio_root() / "studio.pid"
# When the backend is launched directly (bypassing unsloth_cli, which
# normally re-exports these env vars), make sure unsloth-zoo's import-time
# LLAMA_CPP_DEFAULT_DIR binding still picks up the custom-root build. Only
# set when the resolved root is a real custom override -- legacy default
# installs must NOT export, since the installers treat any non-empty
# UNSLOTH_STUDIO_HOME as env-override mode.
# Direct backend launches bypass the CLI's env re-export; do it here for
# real custom roots so unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR
# picks up the custom build. Skip for legacy-default to avoid flipping
# default-mode installs into env-override.
_LEGACY_STUDIO_ROOT = (Path.home() / ".unsloth" / "studio").resolve()
try:
_STUDIO_ROOT_RESOLVED = _studio_root().resolve()

View file

@ -499,7 +499,6 @@ _VLM_MODEL_TYPES = {
# Pre-computed .venv_t5 paths and backend dir for subprocess version switching.
# Vision check uses 5.5.0 (newest, recognizes all architectures).
# Use the shared resolver so custom-root installs find their own venv.
from utils.paths.storage_roots import studio_root as _studio_root # noqa: E402
_VENV_T5_DIR = str(_studio_root() / ".venv_t5_550")

View file

@ -11,14 +11,9 @@ import tempfile
def _infer_studio_home_from_venv() -> Path | None:
"""If running from an installer-managed unsloth_studio venv, return the
parent dir as STUDIO_HOME.
Fallback for fresh shells after a custom install where the installer
wrote to a workspace path but the user did not re-export the env var.
Narrowed via installer-sentinel check (share/studio.conf or bin shim)
so a developer venv that happens to be named ``unsloth_studio`` is not
misidentified as a custom Studio root.
"""Return parent dir of sys.prefix as STUDIO_HOME if running from an
installer-managed unsloth_studio venv. Sentinel-gated (share/studio.conf
or bin shim) so a developer venv named unsloth_studio is not misidentified.
"""
try:
prefix = Path(sys.prefix).resolve()
@ -36,12 +31,10 @@ def _infer_studio_home_from_venv() -> Path | None:
def studio_root() -> Path:
"""Resolve the Studio install root.
"""Studio install root.
Priority: UNSLOTH_STUDIO_HOME env, STUDIO_HOME env (alias), sys.prefix
inference, legacy ~/.unsloth/studio. Backwards-compatible: identical
to the legacy default when no env var is set and we are not running
from the venv.
Priority: UNSLOTH_STUDIO_HOME, STUDIO_HOME, sys.prefix inference,
legacy ~/.unsloth/studio.
"""
override = os.environ.get("UNSLOTH_STUDIO_HOME") or os.environ.get("STUDIO_HOME")
if override:

View file

@ -94,7 +94,6 @@ TRANSFORMERS_DEFAULT_VERSION = "4.57.6"
TRANSFORMERS_5_VERSION = TRANSFORMERS_550_VERSION
# Pre-installed directories — created by setup.sh / setup.ps1.
# Use the shared resolver so custom-root installs find their own venvs.
from utils.paths.storage_roots import studio_root as _studio_root # noqa: E402
_VENV_T5_530_DIR = str(_studio_root() / ".venv_t5_530")

View file

@ -1455,11 +1455,8 @@ if (-not $PythonCmd) {
substep "Using $PythonCmd ($(& $PythonCmd --version 2>&1))"
# The venv must already exist (created by install.ps1).
# This script (setup.ps1 / "unsloth studio update") only updates packages.
# UNSLOTH_STUDIO_HOME / STUDIO_HOME (alias) override the install root,
# mirroring install.ps1. install.ps1 exports UNSLOTH_STUDIO_HOME when
# invoking the studio update.
# The venv must already exist (created by install.ps1); this script only
# updates packages. UNSLOTH_STUDIO_HOME / STUDIO_HOME override the root.
$_studioOverride = if ($env:UNSLOTH_STUDIO_HOME) { $env:UNSLOTH_STUDIO_HOME } elseif ($env:STUDIO_HOME) { $env:STUDIO_HOME } else { $null }
if ($_studioOverride) {
if ($_studioOverride -eq "~" -or $_studioOverride -like "~/*" -or $_studioOverride -like "~\*") {
@ -1728,8 +1725,7 @@ if ($stackExit -ne 0) {
# ── Pre-install transformers 5.x into .venv_t5_530/ and .venv_t5_550/ ──
# Runs outside the deps fast-path gate so that upgrades from the legacy
# single .venv_t5 are always migrated to the tiered layout.
# T5 sidecar venvs live next to the main venv under the resolved $StudioHome
# so custom-root installs are self-contained.
# T5 sidecar venvs live under the resolved $StudioHome so custom installs are self-contained.
$VenvT5_530Dir = Join-Path $StudioHome ".venv_t5_530"
$VenvT5_550Dir = Join-Path $StudioHome ".venv_t5_550"
$VenvT5Legacy = Join-Path $StudioHome ".venv_t5"
@ -1822,15 +1818,12 @@ step "transformers" "5.5.0 pre-installed"
# ==========================================================================
# PHASE 3.4: Prefer prebuilt llama.cpp bundles before source build
# ==========================================================================
# Only nest llama.cpp under $StudioHome when the resolved Studio root is
# a real override (not the legacy default). Compare resolved paths so a
# stale UNSLOTH_STUDIO_HOME pointing at the legacy default does not
# Nest llama.cpp under $StudioHome only for real env-overrides, never the
# legacy default. Stale UNSLOTH_STUDIO_HOME pointing at legacy must not
# accidentally relocate llama.cpp.
$LegacyStudioHome = Join-Path $env:USERPROFILE ".unsloth\studio"
# Canonicalize BOTH sides. $StudioHome is the resolved env-override path
# in env-mode (line 1474) but the bare logical path in default mode
# (line 1480). Canonicalizing on the fly handles a junctioned/symlinked
# %USERPROFILE% the same in both modes.
# Canonicalize both sides so a junctioned/symlinked %USERPROFILE% behaves
# the same in env-mode (resolved) and default mode (logical $StudioHome).
$_studioHomeCanon = $StudioHome
if (Test-Path -LiteralPath $_studioHomeCanon -PathType Container) {
$_studioHomeCanon = (Resolve-Path -LiteralPath $_studioHomeCanon).Path

View file

@ -399,18 +399,14 @@ if [ -d "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator" ] && command -v npm
fi
# ── Python venv + deps ──
# UNSLOTH_STUDIO_HOME / STUDIO_HOME (alias) override the install root, mirroring
# install.sh. install.sh exports UNSLOTH_STUDIO_HOME when invoking this script.
# UNSLOTH_STUDIO_HOME / STUDIO_HOME override the install root (mirrors install.sh).
_studio_override="${UNSLOTH_STUDIO_HOME:-${STUDIO_HOME:-}}"
# Expand a leading ~ / ~/path because env vars are not subject to tilde
# expansion when set with quotes around the value.
case "$_studio_override" in
"~") _studio_override="$HOME" ;;
"~/"*) _studio_override="$HOME/${_studio_override#'~/'}" ;;
esac
if [ -n "$_studio_override" ]; then
mkdir -p -- "$_studio_override"
# CDPATH= prevents CDPATH-prefixed echo; -P canonicalizes symlinks.
STUDIO_HOME="$(CDPATH= cd -P -- "$_studio_override" && pwd -P)" || exit 1
else
STUDIO_HOME="$HOME/.unsloth/studio"
@ -570,16 +566,11 @@ fi
fi
# ── 7. Prefer prebuilt llama.cpp bundles before any source build path ──
# Only nest llama.cpp under $STUDIO_HOME when the resolved Studio root is a
# real override (not the legacy default). Comparing resolved paths instead
# of env-var presence avoids regressing default installs that incidentally
# inherit UNSLOTH_STUDIO_HOME from a parent process or the CLI.
# Nest llama.cpp under $STUDIO_HOME only for real env-overrides, never the
# legacy default. Compare canonicalized paths so STUDIO_HOME (logical in
# default mode, canonical in env mode) and the legacy side line up under
# symlinked $HOME.
_LEGACY_STUDIO_HOME="$HOME/.unsloth/studio"
# Canonicalize BOTH sides under symlinked $HOME. STUDIO_HOME is logical
# in default-mode (line 416 sets it from the bare $HOME path) and
# canonical in env-mode (line 413 uses pwd -P). Canonicalizing on the
# fly here means default-mode under symlinked $HOME still recognizes
# the legacy default and keeps llama.cpp at ~/.unsloth/llama.cpp.
_studio_home_canon="$STUDIO_HOME"
if [ -d "$_studio_home_canon" ]; then
_studio_home_canon=$(CDPATH= cd -P -- "$_studio_home_canon" 2>/dev/null && pwd -P) \

View file

@ -194,10 +194,8 @@ async fn provision_desktop_auth() -> Result<(), String> {
cmd.env_remove("PYTHONPATH");
}
// Tauri does not honor UNSLOTH_STUDIO_HOME / STUDIO_HOME yet; scrub so the
// provisioning subprocess writes to the legacy ~/.unsloth/studio root that
// matches what the Rust auth code reads. UNSLOTH_LLAMA_CPP_PATH is a
// pre-existing backend-only llama.cpp directory override and is kept.
// Tauri uses the legacy root regardless of UNSLOTH_STUDIO_HOME / STUDIO_HOME.
// Scrub so provisioning writes match what the Rust auth code reads.
cmd.env_remove("UNSLOTH_STUDIO_HOME");
cmd.env_remove("STUDIO_HOME");
#[cfg(windows)]

View file

@ -192,10 +192,8 @@ fn spawn_script(
cmd.env_remove("PYTHONPATH");
}
// Tauri only does default-root installs. install.sh / install.ps1 reject
// UNSLOTH_STUDIO_HOME / STUDIO_HOME under --tauri; scrub them from the
// spawned installer so an inherited shell-set value cannot trip the guard.
// UNSLOTH_LLAMA_CPP_PATH is unrelated to the Studio root and kept as-is.
// Tauri only does default-root installs; install.sh / install.ps1 reject
// these under --tauri. Scrub so an inherited value can't trip the guard.
cmd.env_remove("UNSLOTH_STUDIO_HOME");
cmd.env_remove("STUDIO_HOME");

View file

@ -280,12 +280,9 @@ pub fn start_backend(
cmd.env_remove("PYTHONPATH");
}
// The Tauri desktop app does not honor UNSLOTH_STUDIO_HOME / STUDIO_HOME yet;
// it always uses ~/.unsloth/studio. Scrub these from the spawned Python
// backend so it cannot diverge from the Rust-resolved Studio root when the
// user's shell happens to have them set. Keep UNSLOTH_LLAMA_CPP_PATH: that
// is a pre-existing custom-llama.cpp-directory override the Python backend
// and unsloth-zoo intentionally support, independent of the Studio root.
// Tauri uses the legacy root regardless of UNSLOTH_STUDIO_HOME / STUDIO_HOME;
// scrub so the spawned Python backend can't diverge. UNSLOTH_LLAMA_CPP_PATH
// is a pre-existing user-controlled llama.cpp dir override; keep it.
cmd.env_remove("UNSLOTH_STUDIO_HOME");
cmd.env_remove("STUDIO_HOME");

View file

@ -58,9 +58,8 @@ fn spawn_update(
cmd.env_remove("PYTHONPATH");
}
// Tauri still manages the legacy ~/.unsloth/studio root; scrub the custom-
// root env vars so 'unsloth studio update' updates the same install the
// desktop app uses, not an unrelated custom root inherited from the shell.
// Tauri manages the legacy root; scrub so 'unsloth studio update' targets
// the same install the desktop app uses, not an inherited custom root.
cmd.env_remove("UNSLOTH_STUDIO_HOME");
cmd.env_remove("STUDIO_HOME");

View file

@ -21,17 +21,12 @@ import typer
studio_app = typer.Typer(help = "Unsloth Studio commands.")
# UNSLOTH_STUDIO_HOME / STUDIO_HOME (alias) override the install root, matching
# install.sh / install.ps1. Falls back to sys.prefix inference (so direct
# invocations of <STUDIO_HOME>/bin/unsloth still resolve correctly even after
# the installer's env var has expired), then to the legacy ~/.unsloth/studio.
# Returns (path, is_custom): is_custom=True only when the resolved root is a
# real override; we use this to decide whether to re-export the env var to
# child processes.
# Resolve install root: UNSLOTH_STUDIO_HOME / STUDIO_HOME, then sys.prefix
# inference (so a direct call to <root>/bin/unsloth resolves after the
# installer's env var has expired), then legacy ~/.unsloth/studio.
def _looks_like_installer_managed_studio_home(candidate: Path) -> bool:
"""Heuristic: only treat a directory as an installer-managed Studio root
if it carries installer-written sentinels (studio.conf or the bin shim).
Avoids over-matching on a dev venv that happens to be named unsloth_studio.
"""Sentinel check (studio.conf or bin shim) so a dev venv named
unsloth_studio is not misidentified as a custom Studio root.
"""
shim_name = "unsloth.exe" if platform.system() == "Windows" else "unsloth"
return (candidate / "share" / "studio.conf").is_file() or (
@ -58,21 +53,16 @@ def _resolve_studio_home() -> tuple[Path, bool]:
STUDIO_HOME, _STUDIO_HOME_IS_CUSTOM = _resolve_studio_home()
# Re-export only for actual custom roots so child processes (setup script,
# backend run.py) inherit it. Default installs MUST NOT re-export the env
# var, because setup.sh / setup.ps1 treat its presence as "env-override
# mode" and would relocate llama.cpp / DATA_DIR away from legacy paths.
# UNSLOTH_LLAMA_CPP_PATH is also setdefault for custom roots so
# unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR binding picks up the
# correct build dir for GGUF export.
# Re-export only for real custom roots; default installs must NOT export
# (setup.sh/setup.ps1 treat any non-empty UNSLOTH_STUDIO_HOME as env-mode).
# UNSLOTH_LLAMA_CPP_PATH is set so unsloth-zoo's import-time
# LLAMA_CPP_DEFAULT_DIR binding finds the custom build for GGUF export.
if _STUDIO_HOME_IS_CUSTOM:
# Use truthy-check rather than setdefault so a blank env var (e.g.
# UNSLOTH_STUDIO_HOME=) doesn't suppress the inferred custom root.
# Truthy-check (not setdefault) so a blank UNSLOTH_STUDIO_HOME= doesn't
# suppress the inferred custom root.
if not os.environ.get("UNSLOTH_STUDIO_HOME"):
os.environ["UNSLOTH_STUDIO_HOME"] = str(STUDIO_HOME)
# Mirror setup.sh / setup.ps1's legacy-equality check: when an env
# override happens to equal the legacy default, llama.cpp still lives
# at ~/.unsloth/llama.cpp (one shared build across legacy installs).
# When override == legacy default, llama.cpp stays at ~/.unsloth/llama.cpp.
_legacy_studio = (Path.home() / ".unsloth" / "studio").resolve()
if STUDIO_HOME.resolve() == _legacy_studio:
_llama_dir = Path.home() / ".unsloth" / "llama.cpp"