unslothai/unsloth · error · InstallerExit

installer exited {returncode}: {tail or 'no output'}

Error message

installer exited {returncode}: {tail or 'no output'}

What it means

Raised as InstallerExit(returncode, message) when the prebuilt-installer subprocess finishes with a nonzero exit code. The message embeds the last ~1500 characters of captured installer output (tail_lines) so the underlying installer failure is visible. The finally block already cleans up: cancels the watchdog, unregisters the PID, and stops any components the installer announced but never reported stopped.

Source

Thrown at studio/backend/utils/prebuilt/update_flow.py:414

                    announced.discard(child_pid)
                continue
            m = PROGRESS_LINE_RE.search(line)
            if m is None:
                continue
            set_progress(min(float(m.group(1)) / 100.0, 1.0) * DOWNLOAD_PROGRESS_CEILING)
        returncode = proc.wait()
    finally:
        watchdog.cancel()
        if proc.poll() is not None:
            forget_pid(proc.pid)
        # Anything it started and never reported as stopped, whether it timed
        # out, exited nonzero, or died mid-line.
        _stop_announced()
    if timed_out.is_set():
        raise RuntimeError(f"installer timed out after {timeout_seconds}s")
    if returncode != 0:
        tail = "".join(tail_lines).strip()[-1500:]
        raise InstallerExit(returncode, f"installer exited {returncode}: {tail or 'no output'}")


def _new_phase_record(spec: dict) -> dict:
    """Initial breakdown entry for one phase of a chained job."""
    runnable = spec.get("run") is not None
    return {
        "state": PHASE_PENDING if runnable else PHASE_SKIPPED,
        "reason": None if runnable else spec.get("skip_reason"),
        "progress": None,
        "to_tag": None,
        "reload_required": None,
        "message": "",
        "error": None,
    }


def run_chained_update(phases: list[dict], *, job: dict, job_lock: threading.Lock) -> None:
    """Run update phases in order into one shared job dict (the worker of a

View on GitHub (pinned to 203007d190)

Solutions

  1. Read the embedded tail in the error message — it is the installer's own failure output and pinpoints the exact sub-step that failed.
  2. Free disk space and verify write permissions on the install target directory.
  3. Stop other studio processes that may hold locks on files being updated, then re-run the update.
  4. If the tail shows a checksum/verification failure, clear the cached download and retry to re-fetch the artifact.
Defensive patterns

Strategy: try-catch

Validate before calling

import shutil

def can_install(target_dir) -> tuple[bool, str]:
    free = shutil.disk_usage(target_dir).free
    if free < REQUIRED_BYTES:
        return False, f'insufficient disk space: {free} bytes free'
    if not os.access(target_dir, os.W_OK):
        return False, 'target directory not writable'
    return True, ''

Try / catch

try:
    run_update_flow(spec)
except InstallerExit as e:
    log.error('installer failed rc=%s: %s', e.returncode, e.message)
    surface_to_user(e.message)  # tail contains the installer's own diagnosis
    # do not blind-retry; fix the named cause first

Prevention

When it happens

Trigger: The installer subprocess exits with returncode != 0 — failed integrity check of the downloaded artifact, unwritable target directory, version conflict, missing runtime dependency, or the installer's own internal error. The error message carries the installer's stderr/stdout tail for diagnosis.

Common situations: Corrupted or partially-downloaded installer payload, insufficient disk space or permissions at the install target, an already-running process locking files being replaced, or a version mismatch between the installer bootstrap and the runtime it expects.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/7a8d5d585eb13c7f. Report an issue: GitHub.