{"record":{"id":"8754b574467dd4bc","repo":"ultraworkers/claw-code","slug":"cron-registry-lock-poisoned","errorCode":null,"errorMessage":"cron registry lock poisoned","messagePattern":"cron registry lock poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/crates/runtime/src/team_cron_registry.rs","lineNumber":153,"sourceCode":"#[derive(Debug, Clone, Default)]\npub struct CronRegistry {\n    inner: Arc<Mutex<CronInner>>,\n}\n\n#[derive(Debug, Default)]\nstruct CronInner {\n    entries: HashMap<String, CronEntry>,\n    counter: u64,\n}\n\nimpl CronRegistry {\n    #[must_use]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn create(&self, schedule: &str, prompt: &str, description: Option<&str>) -> CronEntry {\n        let mut inner = self.inner.lock().expect(\"cron registry lock poisoned\");\n        inner.counter += 1;\n        let ts = now_secs();\n        let cron_id = format!(\"cron_{:08x}_{}\", ts, inner.counter);\n        let entry = CronEntry {\n            cron_id: cron_id.clone(),\n            schedule: schedule.to_owned(),\n            prompt: prompt.to_owned(),\n            description: description.map(str::to_owned),\n            enabled: true,\n            created_at: ts,\n            updated_at: ts,\n            last_run_at: None,\n            run_count: 0,\n        };\n        inner.entries.insert(cron_id, entry.clone());\n        entry\n    }\n","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/runtime/src/team_cron_registry.rs#L135-L171","documentation":"CronRegistry::create (rust/crates/runtime/src/team_cron_registry.rs:153) panics with .expect(\"cron registry lock poisoned\") when the registry's inner Mutex is poisoned. The lock guards CronInner { entries, counter }; poisoning means a prior thread panicked while that lock was held, so the .expect is reporting earlier damage, not a failure of cron entry creation itself.","triggerScenarios":"Calling CronRegistry::create(schedule, prompt, description) after any thread panicked inside a CronRegistry method (create/get/list/delete/disable/record_run/len) that held the shared lock — for example a panic between inner.counter += 1 and the entries.insert() in a racing create() call.","commonSituations":"Scheduler-driven workloads where cron creation happens on timer threads: one panicking tick poisons the registry and every subsequent CronCreate tool call / scheduler fire panics with this message. Also hit in tests that exercise cron lifecycle concurrently and assert-fail mid-critical-section.","solutions":["Diagnose the original panic (the one that held the lock) from earlier log output; fixing it removes the poisoning.","Restart the process: CronRegistry is in-memory only, so a restart gives a clean mutex.","Change the library to .lock().unwrap_or_else(|e| e.into_inner()) so create() proceeds with a recovered guard; note counter may be mid-increment, so keep the increment inside the same critical section (it already is).","Adopt parking_lot::Mutex for poison-free locking if you can change the dependency.","Ensure the code running inside the registry critical sections cannot panic (no indexing, unwrap, or division in the locked region)."],"exampleFix":"// before\nlet mut inner = self.inner.lock().expect(\"cron 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 created = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    cron_registry.create(schedule, prompt, description)\n}));\nmatch created {\n    Ok(entry) => entry,\n    Err(_) => panic::resume_unwind(make_original()), // or degrade the cron subsystem\n}","preventionTips":["Audit every CronRegistry critical section for panics (indexing, unwrap) — one panic poisons create/get/list/delete/disable/record_run alike.","Prefer parking_lot::Mutex if you control the crate; it has no poisoning.","Run scheduler ticks on threads whose panics are caught, so a bad tick cannot poison the shared registry.","Log panics with a global panic hook so the root-cause panic is captured before the cascade."],"tags":["rust","concurrency","mutex","panic","claw","cron","scheduler"],"backgroundTag":"mutex-poisoned","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}