{"record":{"id":"e3467d44dec4ff38","repo":"ultraworkers/claw-code","slug":"registry-lock-poisoned","errorCode":null,"errorMessage":"registry lock poisoned","messagePattern":"registry lock poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/crates/runtime/src/task_registry.rs","lineNumber":152,"sourceCode":"        &self,\n        packet: TaskPacket,\n    ) -> Result<Task, TaskPacketValidationError> {\n        let packet = validate_packet(packet)?.into_inner();\n        // Use scope_path as description if available, otherwise use scope as string\n        let description = packet\n            .scope_path\n            .clone()\n            .or_else(|| Some(packet.scope.to_string()));\n        Ok(self.create_task(packet.objective.clone(), description, Some(packet)))\n    }\n\n    fn create_task(\n        &self,\n        prompt: String,\n        description: Option<String>,\n        task_packet: Option<TaskPacket>,\n    ) -> Task {\n        let mut inner = self.inner.lock().expect(\"registry lock poisoned\");\n        inner.counter += 1;\n        let ts = now_secs();\n        let task_id = format!(\"task_{:08x}_{}\", ts, inner.counter);\n        let task = Task {\n            task_id: task_id.clone(),\n            prompt,\n            description,\n            task_packet,\n            status: TaskStatus::Created,\n            created_at: ts,\n            updated_at: ts,\n            messages: Vec::new(),\n            output: String::new(),\n            team_id: None,\n            heartbeat: None,\n        };\n        inner.tasks.insert(task_id, task.clone());\n        task","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/runtime/src/task_registry.rs#L134-L170","documentation":"This panic fires in TaskRegistry::create_task (task_registry.rs:152), the shared implementation behind TaskRegistry::create and create_from_packet. It locks the Arc<Mutex<RegistryInner>> guarding the task HashMap and counter; if any thread ever panicked while holding that mutex, the lock is poisoned and this .expect(\"registry lock poisoned\") panics on every subsequent task creation.","triggerScenarios":"Calling TaskRegistry::create(prompt, description) or create_from_packet(packet) after any thread has panicked while holding the registry's inner mutex (e.g. a lane worker panicking inside update/stop/append_output between lock() and guard drop). Task creation is usually the first registry touch, so this is typically where poisoning first becomes visible.","commonSituations":"A background lane/worker thread panics while appending output or updating status on the shared registry; afterwards every new sub-agent task creation aborts the host process. In test suites that share a TaskRegistry across #[tokio::test] or threaded tests, one panicking test poisons the registry for the rest.","solutions":["Enable RUST_BACKTRACE=1 and identify the original panic that held the TaskRegistry lock; fix that (usually an unwrap/expect or index panic inside a registry method or code invoked under the guard).","Make create_task poisoning-tolerant since RegistryInner (HashMap + u64 counter) is plain data: lock().unwrap_or_else(|p| p.into_inner()).","Convert panicking paths inside all TaskRegistry methods to Result-returning error handling so no panic can occur while the guard is alive.","Wrap lane worker entry points in catch_unwind so worker panics never unwind through code holding the registry guard."],"exampleFix":"// before\nlet mut inner = self.inner.lock().expect(\"registry lock poisoned\");\n\n// after\nlet mut inner = self.inner.lock().unwrap_or_else(|poisoned| poisoned.into_inner());","handlingStrategy":"try-catch","validationCode":"use std::panic::{catch_unwind, AssertUnwindSafe};\n\n// cheap pre-flight probe: if this panics, the registry is already poisoned\nlet healthy = catch_unwind(AssertUnwindSafe(|| registry.len())).is_ok();\nif !healthy {\n    eprintln!(\"task registry poisoned — refusing to create task\");\n}","typeGuard":null,"tryCatchPattern":"use std::panic::{catch_unwind, AssertUnwindSafe};\n\nlet task = catch_unwind(AssertUnwindSafe(|| {\n    registry.create(prompt.as_str(), description.as_deref())\n}))\n.unwrap_or_else(|payload| {\n    eprintln!(\"task creation failed — registry poisoned: {payload:?}\");\n    std::process::exit(101);\n});","preventionTips":["Guard lane/worker thread entry points with catch_unwind so panics never hold registry locks.","Return Result from registry methods instead of panicking; keep guards only over plain map/counter updates.","In tests, give each test its own TaskRegistry instead of sharing one Arc across threads.","Set a panic hook that logs thread name and payload to identify the poisoning panic in long-running processes."],"tags":["rust","mutex","panic","concurrency","lock-poisoning","task-registry"],"backgroundTag":"mutex-poisoned","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}