{"record":{"id":"81b0d200d42d5504","repo":"aaif-goose/goose","slug":"failed-to-acquire-registry-lock-81b0d2","errorCode":null,"errorMessage":"Failed to acquire registry lock","messagePattern":"Failed to acquire registry lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-local-inference/src/management.rs","lineNumber":53,"sourceCode":"#[derive(Clone)]\nstruct LocalModelSelection {\n    repo_id: String,\n    backend_id: String,\n    variant_id: Option<String>,\n}\n\npub async fn list_models() -> Result<LocalInferenceModelsListResponse> {\n    ensure_featured_models_current().await?;\n\n    let runtime = management_runtime()?;\n    let recommended_id = recommend_local_model(&runtime);\n\n    let loaded_model_ids = crate::loaded_model_ids()\n        .await\n        .map_err(|error| anyhow!(error.to_string()))?;\n    let registry = get_registry()\n        .lock()\n        .map_err(|_| anyhow!(\"Failed to acquire registry lock\"))?;\n    let mut models: Vec<LocalInferenceModelDto> = registry\n        .list_models()\n        .iter()\n        .map(|entry| local_model_to_dto(entry, &recommended_id, &loaded_model_ids))\n        .collect();\n\n    models.sort_by(|a, b| {\n        let a_downloaded = a.status.state == LocalInferenceDownloadState::Downloaded;\n        let b_downloaded = b.status.state == LocalInferenceDownloadState::Downloaded;\n        match (b_downloaded, a_downloaded) {\n            (true, false) => std::cmp::Ordering::Greater,\n            (false, true) => std::cmp::Ordering::Less,\n            _ => a.id.cmp(&b.id),\n        }\n    });\n\n    Ok(LocalInferenceModelsListResponse { models })\n}","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-local-inference/src/management.rs#L35-L71","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restart the goose process — the mutex is in-memory only; the on-disk registry is reloaded cleanly.","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.","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.","Keep critical sections minimal: clone the needed data out of the registry and drop the guard before doing I/O or downloads."],"exampleFix":"// before\nlet registry = get_registry()\n    .lock()\n    .map_err(|_| anyhow!(\"Failed to acquire registry lock\"))?;\n\n// after: recover from poisoning — registry data is still a valid struct\nlet registry = get_registry()\n    .lock()\n    .unwrap_or_else(|poisoned| poisoned.into_inner());","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match management::list_models().await {\n    Ok(models) => { /* render models */ }\n    Err(e) if e.to_string().contains(\"registry lock\") => {\n        // poisoned mutex: earlier panic corrupted in-process state; restart required\n        show_error(\"Model registry state corrupted by an earlier panic. Restart goose.\");\n    }\n    Err(e) => show_error(e),\n}","preventionTips":["Never call unwrap/expect/unchecked indexing while holding the registry guard.","Keep critical sections minimal: clone needed data, drop the guard, then do I/O.","Restart the process after any panic in download/registry threads instead of continuing.","Consider parking_lot::Mutex (non-poisoning) or .unwrap_or_else(|e| e.into_inner()) recovery for this registry."],"tags":["rust","mutex","poisoning","concurrency","local-inference","registry"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}