unslothai/unsloth · error · SttUnavailableError
The dictation server is not running.
Error message
The dictation server is not running.
What it means
SttUnavailableError raised in transcribe_bytes() after load() returns: under _lock, self._port is None or _process_alive() is False. Something unloaded or killed the server in the gap between load() completing and this check — e.g. the idle-unload timer fired, another thread called unload(), or the process died on its own.
Source
Thrown at studio/backend/core/inference/stt_mtmd_sidecar.py:997
model_id = resolve_mtmd_model_id(model)
if cancel_event is not None and cancel_event.is_set():
raise SttTranscriptionCancelledError("Transcription cancelled.")
# No training guard here on purpose: load() starts the server with
# -ngl 0 --no-mmproj-offload while a run is active, so this transcribes
# on CPU exactly as whisper.cpp and Transformers do. Refusing after a
# preload that succeeded only discarded the user's recording.
# Reject a missing model before decoding, matching the other sidecars.
self._ensure_model_downloaded(model_id)
decoded_audio = _decode_audio_bounded(audio, cancel_event)
if cancel_event is not None and cancel_event.is_set():
raise SttTranscriptionCancelledError("Transcription cancelled.")
wav_bytes = _pcm_to_wav_bytes(decoded_audio)
audio_seconds = (len(decoded_audio) / _TARGET_SAMPLE_RATE) if len(decoded_audio) else None
self.load(model_id, request_cancel_event = cancel_event)
with self._lock:
port = self._port
if port is None or not self._process_alive():
raise SttUnavailableError("The dictation server is not running.")
# Another client can switch models in the gap between that load
# returning and this lock, and the port read here would then be its
# server. Refuse rather than transcribe on the wrong model.
if self._model_id != model_id:
raise SttModelBusyError(
"The dictation model changed while this recording was being "
"prepared. Try again."
)
# Long audio can outlast the keep-alive, and _post_transcribe runs
# outside the lock, so disarm the timer rather than let it kill
# llama-server mid-request and throw the dictation away.
self._active_requests += 1
self._cancel_idle_unload_locked()
try:
# Outside the lock: a held lock would block unload, including the
# one a training run performs, for the whole request timeout.
text = self._post_transcribe(
port, model_id, wav_bytes, audio_seconds, cancel_event = cancel_eventView on GitHub (pinned to 203007d190)
Solutions
- Retry the transcription — the retry's load() will bring a fresh server up.
- If it recurs, check whether the idle-unload keep-alive is shorter than your decode time and raise it.
- Inspect llama-server logs for a crash (OOM, bad file) if no unload was requested.
Defensive patterns
Strategy: retry
Try / catch
```python
for attempt in range(2):
try:
return sidecar.transcribe_bytes(audio, cancel_event=ev)
except SttUnavailableError as exc:
if "not running" not in str(exc) or attempt:
raise
continue # load() on retry brings a fresh server
``` Prevention
- Keep the idle-unload keep-alive longer than worst-case decode time.
- Avoid calling unload() concurrently with in-flight transcriptions.
- Watch llama-server stability (OOM) if this fires with no unload in sight.
When it happens
Trigger: load() succeeds, then before the transcribe critical section runs, the server is unloaded (idle timeout, training preempt, crash) leaving _port None or a dead process.
Common situations: Idle keep-alive expiry racing a slow decode; training run unloading sidecars during audio preparation; llama-server crashing under OOM right after startup.
Related errors
- The local transcription runtime is being updated. Try dictat
- Transcription cancelled.
- llama-server did not become ready for '{model_id}'.
- The dictation model changed while this recording was being p
- A transformers installation is replacing the latest sidecar;
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/e97a46b1c2b9122c.
Report an issue: GitHub.