aaif-goose/goose · error
Failed to lock transcriber: {}
Error message
Failed to lock transcriber: {} What it means
The local Whisper transcriber is cached in a global std::sync::Mutex (LOCAL_TRANSCRIBER) shared across calls so the model loads once. `.lock()` returns Err only when the mutex is poisoned — another thread panicked while holding it (model load, file read, or inference panic). The {error} is the std::sync::PoisonError.
Source
Thrown at crates/goose/src/dictation/providers.rs:150
}
#[cfg(feature = "local-inference")]
pub async fn transcribe_local(audio_bytes: Vec<u8>) -> Result<String> {
tokio::task::spawn_blocking(move || {
let config = Config::global();
let model_id = config
.get(LOCAL_WHISPER_MODEL_CONFIG_KEY, false)
.ok()
.and_then(|v| v.as_str().map(|s| s.to_string()))
.ok_or_else(|| anyhow::anyhow!("Local Whisper model not configured"))?;
let model = super::whisper::get_model(&model_id)
.ok_or_else(|| anyhow::anyhow!("Unknown model: {}", model_id))?;
let model_path = model.local_path();
let mut transcriber_lock = LOCAL_TRANSCRIBER
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock transcriber: {}", e))?;
let model_path_str = model_path.to_string_lossy().to_string();
let needs_reload = match transcriber_lock.as_ref() {
None => true,
Some((cached_path, _)) => cached_path != &model_path_str,
};
if needs_reload {
tracing::info!("Loading Whisper model from: {}", model_path.display());
let transcriber = super::whisper::WhisperTranscriber::new_with_tokenizer(
&model_id,
&model_path,
WHISPER_TOKENIZER_JSON,
)?;
*transcriber_lock = Some((model_path_str, transcriber));
}View on GitHub (pinned to 3810898a74)
Solutions
- Look upward in the logs for the ORIGINAL panic that poisoned the mutex — fixing that is the real fix
- Restart the process: poisoning is per-process state and a fresh start clears it
- If the model file is corrupt (partial download), remove it so the next start re-downloads cleanly
Defensive patterns
Strategy: try-catch
Try / catch
match transcribe_local(audio).await {
Err(e) if e.to_string().contains("Failed to lock transcriber") => {
// mutex poisoned by an earlier panic: surface the original panic from logs,
// stop accepting dictation, and restart the process to recover
}
other => other,
} Prevention
- Catch panics inside the transcription path so a model-load failure cannot poison LOCAL_TRANSCRIBER (e.g. std::panic::catch_unwind around model load/inference)
- Verify model files are fully downloaded before enabling local dictation (is_downloaded)
- Re-download the model when files are truncated or corrupted after an interrupted download
When it happens
Trigger: A previous transcribe_local call panicked inside spawn_blocking while holding the lock (corrupted model file, decode error, IO panic); every subsequent transcription in the same process then fails immediately with this error.
Common situations: Interrupted model download leaving a truncated file; disk errors while mmapping the model; a panic during transcription after which the process keeps serving requests.
Related errors
- Failed to acquire registry lock
- Failed to acquire registry lock
- Failed to acquire lock
- Failed to acquire registry lock
- Local Whisper model not configured
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/a154ae02a272d6b8.
Report an issue: GitHub.