unslothai/unsloth · error · RuntimeError

failed to spawn sd-server: {self._spawn_error}

Error message

failed to spawn sd-server: {self._spawn_error}

What it means

Raised when the background thread that spawns the sd-server process records a spawn error (exec failure, missing binary, permission denied). The wrapper disposes the half-started process and surfaces the underlying spawn error string. This happens before any readiness polling — the process never came up.

Source

Thrown at studio/backend/core/inference/sd_cpp_server.py:276

                self._process = proc
                self.port = port
                adopt_pid(proc.pid)  # so a global shutdown sweep also reaps it
                spawned.set()
                self._drain_stdout(proc)
                # stdout closed == process exited; reap it so it is not left a zombie.
                try:
                    proc.wait(timeout = 5)
                except Exception:  # noqa: BLE001
                    pass

            self._stdout_thread = threading.Thread(
                target = _own_process, daemon = True, name = "sd-server-owner"
            )
            self._stdout_thread.start()
            spawned.wait()
            if self._spawn_error is not None:
                self._dispose()
                raise RuntimeError(f"failed to spawn sd-server: {self._spawn_error}")
            if not self._wait_ready(startup_timeout):
                tail = _diagnostic_tail(self._tail, keep = 30)
                aborted = self._abort.is_set()
                self._kill_locked()
                self._dispose()
                if aborted:
                    raise SdCppCancelled("sd-server startup was cancelled.")
                raise RuntimeError("sd-server failed to become ready. Last output:\n" + tail[:2000])

    def _wait_ready(
        self,
        timeout: float,
        interval: float = 0.5,
    ) -> bool:
        """Poll ``/v1/models`` until 200; bail early if the process exits.

        Upstream binds the port only AFTER the model is loaded, so a 200 here is a true
        ready signal (no half-loaded race)."""

View on GitHub (pinned to 203007d190)

Solutions

  1. Read the {self._spawn_error} part — it is the OS-level reason (ENOENT, EACCES, etc.).
  2. ENOENT/EACCES: restore the binary (`unsloth studio update`) or fix the executable bit and interpreter availability.
  3. Loader errors: install the paired native libraries the server ships with; do not copy the binary out of its bundle directory.
Defensive patterns

Strategy: try-catch

Validate before calling

binary = find_server_binary()
if binary is None or not os.access(binary, os.X_OK):
    raise SystemExit("sd-server binary missing or not executable; run unsloth studio update")

Try / catch

try:
    server.start()
except RuntimeError as e:
    if "failed to spawn" in str(e):
        reinstall_runtime(); server.start()
    raise

Prevention

When it happens

Trigger: Starting the sd-server wrapper when its binary path is missing, not executable, or fails to exec (bad ELF, wrong architecture, missing shared libraries at exec time).

Common situations: Broken or partial runtime install after a failed update; binary replaced by a non-executable file; running on an OS/arch the build does not support.

Related errors


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