zellij-org/zellij · critical · anyhow::Error

cannot acquire poisoned lock for {e:#?}

Error message

cannot acquire poisoned lock for {e:#?}

What it means

Debug-mode variant of the ToAnyhow implementation for std::sync::PoisonError in zellij-utils/errors.rs. A Mutex/RwLock became poisoned because some thread panicked while holding it, and now every subsequent .lock() on that lock fails and is converted to this error (including the poison payload in DEBUG_MODE). The lock itself is fine; the poison flag records that a holder bailed out mid-critical-section, so guarded state may be inconsistent.

Source

Thrown at zellij-utils/src/errors.rs:976

                            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 98a0837077)

Solutions

  1. Find the original panic in the log (the first panic, not the poison errors) — fix or report that; the poison cascade is downstream
  2. Restart the zellij session; poison state cannot be cleared in-process
  3. Upgrade zellij; several panics-under-lock bugs were fixed over releases
  4. As a developer: keep critical sections panic-free (no unwrap/expect under locks) and shrink lock scope so a panic is unlikely inside it

Example fix

// before
let state = shared.lock().to_anyhow()?; // any later thread fails once poisoned

// after: one designated recovery point, deliberate policy
let state = match shared.lock() {
    Ok(g) => g,
    Err(poisoned) => {
        log::error!("lock poisoned, recovering guard; state may be inconsistent");
        poisoned.into_inner()
    },
};
Defensive patterns

Strategy: fallback

Try / catch

use std::sync::{Mutex, PoisonError};

fn lock_or_recover<T>(m: &Mutex<T>) -> Result<std::sync::MutexGuard<'_, T>, PoisonError<std::sync::MutexGuard<'_, T>>> {
    // deliberate policy at one designated recovery point: state guarded by this
    // lock is tolerant to partial writes, so recover the guard instead of failing
    m.lock().map_err(|p| { log::error!("lock poisoned, recovering: {p}"); p })
}
// usage: lock_or_recover(&shared).unwrap_or_else(|p| p.into_inner()).update();

Prevention

When it happens

Trigger: Any panic (unwrap, index out of bounds, assertion) inside a critical section guarded by the affected Mutex/RwLock, followed by any other thread attempting to lock it and piping the result through .to_anyhow()?.

Common situations: A rendering/pane-handling bug panics once while holding a shared lock; every later operation touching that lock then fails with poisoned-lock errors, cascading across threads. Almost always a zellij-internal bug, not user misconfiguration.

Related errors


AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16). Data as JSON: /api/errors/56ffbb37ae973fc3. Report an issue: GitHub.