ultraworkers/claw-code · error
team registry lock poisoned
Error message
team registry lock poisoned
What it means
This panic fires in TeamRegistry::create (team_cron_registry.rs:68), which allocates a team_{timestamp}_{counter} id and inserts a new Team into the shared Arc<Mutex<TeamInner>> (HashMap + counter). The expect fires when that mutex is poisoned — some thread previously panicked while holding it — making every subsequent team creation panic.
Source
Thrown at rust/crates/runtime/src/team_cron_registry.rs:68
#[derive(Debug, Clone, Default)]
pub struct TeamRegistry {
inner: Arc<Mutex<TeamInner>>,
}
#[derive(Debug, Default)]
struct TeamInner {
teams: HashMap<String, Team>,
counter: u64,
}
impl TeamRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn create(&self, name: &str, task_ids: Vec<String>) -> Team {
let mut inner = self.inner.lock().expect("team registry lock poisoned");
inner.counter += 1;
let ts = now_secs();
let team_id = format!("team_{:08x}_{}", ts, inner.counter);
let team = Team {
team_id: team_id.clone(),
name: name.to_owned(),
task_ids,
status: TeamStatus::Created,
created_at: ts,
updated_at: ts,
};
inner.teams.insert(team_id, team.clone());
team
}
pub fn get(&self, team_id: &str) -> Option<Team> {
let inner = self.inner.lock().expect("team registry lock poisoned");
inner.teams.get(team_id).cloned()View on GitHub (pinned to 08106b0c37)
Solutions
- Reproduce with RUST_BACKTRACE=full and fix the first panic that held the TeamRegistry lock; create() is downstream of it.
- Recover the guard — TeamInner is a plain HashMap plus u64 counter: lock().unwrap_or_else(|p| p.into_inner()).
- Remove panicking constructs from all TeamRegistry critical sections (use Result-returning error paths).
- Wrap orchestration worker entry points in catch_unwind so panics cannot unwind through team mutations.
Example fix
// before
let mut inner = self.inner.lock().expect("team registry lock poisoned");
// after
let mut inner = self.inner.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); Defensive patterns
Strategy: try-catch
Validate before calling
use std::panic::{catch_unwind, AssertUnwindSafe};
// pre-flight: verify the team registry is usable before orchestration
let healthy = catch_unwind(AssertUnwindSafe(|| !registry.list().is_empty() || true)).is_ok();
if !healthy {
eprintln!("team registry poisoned — aborting team creation");
} Try / catch
use std::panic::{catch_unwind, AssertUnwindSafe};
let team = catch_unwind(AssertUnwindSafe(|| registry.create(name, task_ids)))
.unwrap_or_else(|payload| {
eprintln!("team creation failed — registry poisoned: {payload:?}");
std::process::exit(101);
}); Prevention
- Wrap orchestration workers in catch_unwind before they touch shared team state.
- Give each integration test an isolated TeamRegistry; a panicking test must not poison shared state.
- Keep create() limited to map/counter updates — move validation and formatting outside the guard.
When it happens
Trigger: Calling TeamRegistry::create(name, task_ids) after any thread panicked while holding the TeamRegistry inner mutex (in create, delete, remove, or any other TeamRegistry method on the shared instance).
Common situations: Team/cron orchestration where a worker thread panicked during team mutation; afterwards every attempt to group tasks into a new team aborts the CLI process. Also seen in threaded tests sharing one TeamRegistry when a prior test panicked.
Related errors
- registry lock poisoned
- lsp registry lock poisoned
- mcp registry lock poisoned
- cron registry lock poisoned
- worker registry lock poisoned
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/1569db791faad0d0.
Report an issue: GitHub.