{"record":{"id":"2020b11f1300073a","repo":"ultraworkers/claw-code","slug":"worker-registry-lock-poisoned","errorCode":null,"errorMessage":"worker registry lock poisoned","messagePattern":"worker registry lock poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/crates/runtime/src/worker_boot.rs","lineNumber":313,"sourceCode":"struct WorkerRegistryInner {\n    workers: HashMap<String, Worker>,\n    counter: u64,\n}\n\nimpl WorkerRegistry {\n    #[must_use]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    #[must_use]\n    pub fn create(\n        &self,\n        cwd: &str,\n        trusted_roots: &[String],\n        auto_recover_prompt_misdelivery: bool,\n    ) -> Worker {\n        let mut inner = self.inner.lock().expect(\"worker registry lock poisoned\");\n        inner.counter += 1;\n        let ts = now_secs();\n        let worker_id = format!(\"worker_{:08x}_{}\", ts, inner.counter);\n        let trust_auto_resolve = trusted_roots\n            .iter()\n            .any(|root| path_matches_allowlist(cwd, root));\n        let mut worker = Worker {\n            worker_id: worker_id.clone(),\n            cwd: cwd.to_owned(),\n            status: WorkerStatus::Spawning,\n            trust_auto_resolve,\n            trust_gate_cleared: false,\n            auto_recover_prompt_misdelivery,\n            prompt_delivery_attempts: 0,\n            prompt_in_flight: false,\n            prompt_sent_at: None,\n            last_prompt: None,\n            expected_receipt: None,","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/runtime/src/worker_boot.rs#L295-L331","documentation":"WorkerRegistry::create (rust/crates/runtime/src/worker_boot.rs:313) panics at .expect(\"worker registry lock poisoned\") when the worker registry's inner Mutex is poisoned. The critical section allocates worker_<ts>_<counter> ids, evaluates trust_auto_resolve against trusted_roots via path_matches_allowlist, and inserts the new Worker — the panic indicates an earlier panic by another thread while this same lock was held.","triggerScenarios":"Calling WorkerRegistry::create(cwd, trusted_roots, auto_recover_prompt_misdelivery) after any thread panicked inside a WorkerRegistry method (create/get/observe/observe_startup_preflight/resolve_trust/send_prompt/restart/terminate/observe_completion/observe_startup_timeout) that held the shared lock — e.g. a panic inside observe()'s screen-text classification unwinding mid-critical-section.","commonSituations":"Multi-worker agent lanes: one worker's observe() panicking on unexpected state poisons the registry, and then spawning any new worker (create) dies with 'worker registry lock poisoned', halting lane bootstrapping until restart.","solutions":["Track down the first panic that held the worker registry lock (earlier backtrace); fixing it removes the poison source.","Restart the control-plane process — the registry is in-memory, so poisoning is not persisted.","Recover the guard in the crate: .lock().unwrap_or_else(|e| e.into_inner()); verify counter/worker state consistency before inserting the new worker.","Adopt parking_lot::Mutex (no poisoning) for this registry.","Keep panicky logic (terminal screen parsing, path matching) out of the locked region where possible, or wrap create() callers in catch_unwind."],"exampleFix":"// before\nlet mut inner = self.inner.lock().expect(\"worker registry lock poisoned\");\ninner.counter += 1;\n\n// after\nlet mut inner = self\n    .inner\n    .lock()\n    .unwrap_or_else(|poisoned| poisoned.into_inner());\ninner.counter += 1;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let worker = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    worker_registry.create(cwd, trusted_roots, auto_recover)\n}));\nmatch worker {\n    Ok(w) => w,\n    Err(_) => return Err(\"worker registry poisoned; cannot spawn workers until restart\"),\n}","preventionTips":["Audit all WorkerRegistry critical sections (observe/send_prompt/observe_completion) for panics — one poison stops all worker creation.","Move panicky classification logic outside the locked region to shrink the poison window.","Recover guards with into_inner() or adopt parking_lot::Mutex if you maintain the crate.","Run each worker's supervision in a thread with catch_unwind so a single worker panic doesn't take down spawning."],"tags":["rust","concurrency","mutex","panic","claw","worker","lane"],"backgroundTag":"mutex-poisoned","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}