cjpais/Handy · error · anyhow::Error
Model is not loaded for transcription.
Error message
Model is not loaded for transcription.
What it means
transcribe() first waits on a condvar until any in-progress model load finishes, then requires the engine slot to be occupied. This error means the engine is None at that point: no model was ever loaded, the last load failed, the model was unloaded (idle timeout / manual unload / model switch), or the engine was dropped after a prior panic. Despite the comment, the current code performs no auto-load — it errors immediately.
Solutions
- Open settings and confirm a model is downloaded and selected; wait for the model-state-changed "loaded" event before recording
- Check logs/app notifications for a preceding loading_failed error and fix that first (usually re-download)
- Increase or disable the model unload timeout if the engine keeps getting evicted between recordings
- If it follows a panic error, simply retry — the next attempt reloads the model
Example fix
// before: record blind, fail when idle-unload evicted the engine
let text = manager.transcribe(audio)?;
// after: ensure a model is active first (loads synchronously if needed)
if manager.get_current_model().is_none() {
manager.load_model_blocking(&settings.selected_model, None)?;
}
let text = manager.transcribe(audio)?; Defensive patterns
Strategy: validation
Validate before calling
// before triggering a recording: ensure a model is actually resident
if manager.get_current_model().is_none() {
manager.load_model_blocking(&settings.selected_model, None)?;
} Type guard
// Rust analog of a type guard: narrow on manager state before transcribe()
fn model_ready(m: &TranscriptionManager) -> bool {
m.get_current_model().is_some() && !m.is_loading()
} Try / catch
match manager.transcribe(audio) {
Err(e) if e.to_string() == "Model is not loaded for transcription." => {
manager.load_model_blocking(&settings.selected_model, None)?;
manager.transcribe(audio) // one retry after load
}
other => other,
} Prevention
- Gate the record shortcut on the frontend's model-ready state (model-state-changed "loaded")
- Raise or disable the model unload timeout if evictions happen between recordings
- Handle download completion before allowing first recording on a fresh install
- After any load failure, re-trigger a load before the next record attempt
When it happens
Trigger: Global shortcut triggers transcription before the first model finishes downloading/loading; the selected model's load failed (a loading_failed event preceded this); ModelUnloadTimeout elapsed since last use and the engine was unloaded; a previous transcription panicked (error 57) and the engine was not returned.
Common situations: Fresh install with no model downloaded yet; user switched models in settings and recorded during the swap; aggressive unload-timeout setting on a slow machine; earlier load failure user dismissed.
Related errors
- Model failed to load after auto-load attempt. Please check…
- Model not available
- Canary transcription failed
- Cohere transcription failed
- Complete model directory not found
AI-assisted analysis of cjpais/Handy@ba10ce1943 (2026-08-17).
Data as JSON: /api/errors/804d59fc741194d4.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/managers/transcription.rs:1208
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 ba10ce1943)