aaif-goose/goose · error

Failed to acquire registry lock

Error message

Failed to acquire registry lock

What it means

list_models() takes the global registry mutex via get_registry() (a std::sync::Mutex in local_model_registry.rs). The error means lock() returned Err, i.e. the mutex is poisoned: some other thread panicked while holding this lock, and std marks the mutex poisoned forever after. Every subsequent list_models() call in this process then fails with this message. The registry data itself is fine (persisted on disk); only the in-process lock is unusable.

Source

Thrown at crates/goose-local-inference/src/management.rs:53

#[derive(Clone)]
struct LocalModelSelection {
    repo_id: String,
    backend_id: String,
    variant_id: Option<String>,
}

pub async fn list_models() -> Result<LocalInferenceModelsListResponse> {
    ensure_featured_models_current().await?;

    let runtime = management_runtime()?;
    let recommended_id = recommend_local_model(&runtime);

    let loaded_model_ids = crate::loaded_model_ids()
        .await
        .map_err(|error| anyhow!(error.to_string()))?;
    let registry = get_registry()
        .lock()
        .map_err(|_| anyhow!("Failed to acquire registry lock"))?;
    let mut models: Vec<LocalInferenceModelDto> = registry
        .list_models()
        .iter()
        .map(|entry| local_model_to_dto(entry, &recommended_id, &loaded_model_ids))
        .collect();

    models.sort_by(|a, b| {
        let a_downloaded = a.status.state == LocalInferenceDownloadState::Downloaded;
        let b_downloaded = b.status.state == LocalInferenceDownloadState::Downloaded;
        match (b_downloaded, a_downloaded) {
            (true, false) => std::cmp::Ordering::Greater,
            (false, true) => std::cmp::Ordering::Less,
            _ => a.id.cmp(&b.id),
        }
    });

    Ok(LocalInferenceModelsListResponse { models })
}

View on GitHub (pinned to 3810898a74)

Solutions

  1. Restart the goose process — the mutex is in-memory only; the on-disk registry is reloaded cleanly.
  2. Find the FIRST panic in the logs (search for 'panicked at'); this lock error is only a symptom. Fix the panic inside that critical section.
  3. Audit all code holding the registry guard for unwrap/expect/indexing (delete_model, add_model, sync_with_featured, list_models_mut loops) and make them return Result instead of panicking.
  4. Keep critical sections minimal: clone the needed data out of the registry and drop the guard before doing I/O or downloads.

Example fix

// before
let registry = get_registry()
    .lock()
    .map_err(|_| anyhow!("Failed to acquire registry lock"))?;

// after: recover from poisoning — registry data is still a valid struct
let registry = get_registry()
    .lock()
    .unwrap_or_else(|poisoned| poisoned.into_inner());
Defensive patterns

Strategy: try-catch

Try / catch

match management::list_models().await {
    Ok(models) => { /* render models */ }
    Err(e) if e.to_string().contains("registry lock") => {
        // poisoned mutex: earlier panic corrupted in-process state; restart required
        show_error("Model registry state corrupted by an earlier panic. Restart goose.");
    }
    Err(e) => show_error(e),
}

Prevention

When it happens

Trigger: Calling the local-inference model list API after any thread panicked inside a registry critical section — e.g. a panic in delete_model, add_model during download registration, or sync_with_featured/enrich inside ensure_featured_models_current() (which runs first in list_models). Any unwrap/expect/index-out-of-bounds executed while the guard is held poisons the mutex.

Common situations: A background download thread panicking mid-write to registry.json (disk full, permission denied, serialization bug); a crash during featured-model backfill at startup; concurrent CLI and desktop sessions in one process; upgrading goose versions where a registry field parse panicked in an old code path.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/81b0d200d42d5504. Report an issue: GitHub.