zellij-org/zellij · error
cannot acquire poisoned lock for
Error message
cannot acquire poisoned lock for {e:#?} What it means
Produced by the ToAnyhow impl for Result<_, std::sync::PoisonError<U>> when locking a std::sync::Mutex (or RwLock) whose poison flag is set: some thread panicked while holding the lock, so every subsequent .lock().to_anyhow() fails with this message. The DEBUG_MODE variant prints the PoisonError itself. It is always a secondary error — the original panic is the real fault.
Solutions
- Find the FIRST panic in the log/backtrace (RUST_BACKTRACE=1) — fix or report that; the poison message only tells you which lock was held.
- Kill and restart the wedged session (zellij kill-session <name>) — poisoned state does not recover in-process.
- If reproducible, reduce to the key/mouse sequence or layout that triggers the original panic and file an upstream issue with both backtraces.
- Update zellij — the panic that poisons the lock is usually already fixed.
Example fix
// before: panic while holding the lock poisons it for everyone let state = shared.lock().unwrap(); do_work_that_may_panic(&state); // panic here poisons the mutex // after: keep panics out of the critical section let snapshot = shared.lock().unwrap().clone(); do_work_that_may_panic(&snapshot); // lock already released
Defensive patterns
Strategy: fallback
Try / catch
// recovering shared state after poisoning (data may be mid-update: validate!)
let guard = match mutex.lock() {
Ok(g) => g,
Err(poisoned) => {
log::error!("mutex poisoned by earlier panic; recovering guard");
poisoned.into_inner()
},
}; Prevention
- Never panic inside a critical section: clone data out of the lock and do fallible work after releasing it.
- Prefer lock scopes measured in statements; avoid .unwrap()/.expect()/indexing while holding the mutex.
- Hunt the FIRST panic in logs whenever a poison error appears — the lock message is only the echo.
When it happens
Trigger: Any earlier panic inside a critical section (unwrapped Option/Result, index out of bounds, assertion) poisons the mutex; the next thread to acquire the lock gets Err(PoisonError) and converts it via to_anyhow(). In zellij this guards shared state like session/server bookkeeping.
Common situations: A rendering, input, or plugin thread panicked (often visible earlier in the log) and afterwards every user of that shared state reports 'cannot acquire poisoned lock'; typically the session becomes unresponsive and needs to be killed.
Related errors
- cannot acquire poisoned lock
- Character key needs character data
- Failed to find active pane
- failed to find active pane id for client
- Failed to find pane
AI-assisted analysis of zellij-org/zellij@bf8d23a4f7 (2026-08-19).
Data as JSON: /api/errors/56ffbb37ae973fc3.
Report an issue: GitHub.
Appendix: source
Thrown at zellij-utils/src/errors.rs:986
msg
))
.with_context(|| context.to_string())
} else {
Err(anyhow::anyhow!("failed to send message to channel"))
.with_context(|| context.to_string())
}
},
}
}
}
impl<U> ToAnyhow<U> for Result<U, std::sync::PoisonError<U>> {
fn to_anyhow(self) -> anyhow::Result<U> {
match self {
Ok(val) => anyhow::Ok(val),
Err(e) => {
if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
Err(anyhow::anyhow!("cannot acquire poisoned lock for {e:#?}"))
} else {
Err(anyhow::anyhow!("cannot acquire poisoned lock"))
}
},
}
}
}
}
View on GitHub (pinned to bf8d23a4f7)