cjpais/Handy · error · anyhow::Error
Model is not loaded for transcription.
Error message
Model is not loaded for transcription.
What it means
transcribe() requires a loaded engine: after waiting on the loading condvar (so any in-flight load finishes), the engine slot is still None, so the call fails immediately. This happens when the initial load failed (you also saw a 'Failed to load ... model' error and a loading_failed event), when the idle watcher unloaded the model, or after a panic unload — and nothing re-initiated a load before transcribe() was called. Note load_model drops the old engine before building the new one, so a failed model switch also leaves the slot empty.
Source
Thrown at src-tauri/src/managers/transcription.rs:1182
debug!("Audio vector length: {}", audio_len);
if audio.is_empty() {
debug!("Empty audio vector");
self.maybe_unload_immediately("empty audio");
return Ok(String::new());
}
// Check if model is loaded, if not try to load it
{
// If the model is loading, wait for it to complete.
let mut is_loading = self.is_loading.lock().unwrap();
while *is_loading {
is_loading = self.loading_condvar.wait(is_loading).unwrap();
}
let engine_guard = self.lock_engine();
if engine_guard.is_none() {
return Err(anyhow::anyhow!("Model is not loaded for transcription."));
}
}
// Get current settings for configuration
let settings = get_settings(&self.app_handle);
// Validate selected language against the model's supported languages.
// If the language isn't supported, fall back to "auto" to prevent errors.
// Validate against the model that's actually loaded (which can differ
// from settings.selected_model when a caller loaded a specific model —
// e.g. the --transcribe-file path's --model), not the persisted
// selection.
let active_model = self
.get_current_model()
.unwrap_or_else(|| settings.selected_model.clone());
// Resolve the persisted language *intent* into the language this model
// will actually use. The coercion is capability-aware (a must-pick model
// never receives "auto") and computed fresh here — it is never writtenView on GitHub (pinned to 98a4d80cce)
Solutions
- Trigger a load before transcribing: call initiate_model_load() (or simply start a new recording, which kicks the load) and wait for the loading_completed event
- Check the last loading_failed model-state-changed event — the underlying load error (missing files, OOM) must be fixed first
- Verify settings.selected_model still references an installed model; re-select or re-download it
- Disable or lengthen idle auto-unload if recordings frequently race the unload watcher
- If it recurs after a panic, see the 'Transcription engine panicked' error — the unload there is intentional
Example fix
// before
let text = tm.transcribe(audio)?; // fails with 'Model is not loaded for transcription.'
// after — ensure a load is in flight and wait for it
if !tm.is_model_loaded() {
tm.initiate_model_load(); // background load; UI shows model-state-changed events
wait_for_loading_completion(&app_handle); // block/await until loading_completed or loading_failed
}
anyhow::ensure!(tm.is_model_loaded(), "model failed to load — check model settings");
let text = tm.transcribe(audio)?; Defensive patterns
Strategy: validation
Validate before calling
// Ensure a model is loaded (or a load is in flight) before transcribing
if !tm.is_model_loaded() {
tm.initiate_model_load();
wait_for_model_state(&app_handle, |ev| ev.event_type == "loading_completed" || ev.event_type == "loading_failed")?;
}
anyhow::ensure!(tm.is_model_loaded(), "model unavailable — fix the load error first"); Type guard
// Narrow on the runtime model state before calling transcribe
fn engine_ready(tm: &TranscriptionManager) -> bool {
tm.is_model_loaded() // true only when the engine slot is occupied
} Try / catch
match tm.transcribe(audio) {
Err(e) if e.to_string().contains("Model is not loaded") => {
tm.initiate_model_load();
wait_for_loading_completion(&app_handle);
tm.transcribe(audio) // retry after load
}
other => other,
} Prevention
- Always call initiate_model_load() at recording start and await the loading_completed event before transcribing
- React to model-state-changed/loading_failed events by surfacing the cause instead of letting transcribe fail later
- Disable or lengthen idle auto-unload if recordings race the unload watcher
- Keep settings.selected_model pointing at a model that is actually installed and loads cleanly
When it happens
Trigger: transcribe() invoked while engine_guard.is_none(): recording started before the first model download finished (or after it failed); model auto-unloaded by the idle watcher and the shortcut path called transcribe() without initiate_model_load(); previous load_model failed mid-switch after dropping the old engine (lines 526-533); engine was dropped by the panic-recovery path in error 57.
Common situations: First run with no model downloaded yet; persisted selected_model points at a model whose files were deleted; idle-unload enabled with aggressive timeout; user switched models right as a recording ended; earlier load failed due to missing/corrupt files.
Related errors
- Model failed to load after auto-load attempt. Please check y
- Failed to resolve VAD path: {}
- Model not downloaded
- Failed to load moonshine model {}: {}
- Failed to load moonshine streaming model {}: {}
AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16).
Data as JSON: /api/errors/804d59fc741194d4.
Report an issue: GitHub.