ultraworkers/claw-code · error
cron registry lock poisoned
Error message
cron registry lock poisoned
What it means
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.
Source
Thrown at rust/crates/runtime/src/team_cron_registry.rs:153
#[derive(Debug, Clone, Default)]
pub struct CronRegistry {
inner: Arc<Mutex<CronInner>>,
}
#[derive(Debug, Default)]
struct CronInner {
entries: HashMap<String, CronEntry>,
counter: u64,
}
impl CronRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn create(&self, schedule: &str, prompt: &str, description: Option<&str>) -> CronEntry {
let mut inner = self.inner.lock().expect("cron registry lock poisoned");
inner.counter += 1;
let ts = now_secs();
let cron_id = format!("cron_{:08x}_{}", ts, inner.counter);
let entry = CronEntry {
cron_id: cron_id.clone(),
schedule: schedule.to_owned(),
prompt: prompt.to_owned(),
description: description.map(str::to_owned),
enabled: true,
created_at: ts,
updated_at: ts,
last_run_at: None,
run_count: 0,
};
inner.entries.insert(cron_id, entry.clone());
entry
}
View on GitHub (pinned to 08106b0c37)
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).
Example fix
// before
let mut inner = self.inner.lock().expect("cron registry lock poisoned");
inner.counter += 1;
// after
let mut inner = self
.inner
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
inner.counter += 1; Defensive patterns
Strategy: try-catch
Try / catch
let created = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cron_registry.create(schedule, prompt, description)
}));
match created {
Ok(entry) => entry,
Err(_) => panic::resume_unwind(make_original()), // or degrade the cron subsystem
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- worker registry lock poisoned
- registry lock poisoned
- team registry lock poisoned
- lsp registry lock poisoned
- mcp registry lock poisoned
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/8754b574467dd4bc.
Report an issue: GitHub.