xai-org/grok-build · error
local-workspace intent mutex poisoned; refuse attach (fail c
Error message
local-workspace intent mutex poisoned; refuse attach (fail closed)
What it means
The process-global ACTIVE_LOCAL_WORKSPACE Mutex<Option<LocalWorkspaceConfig>> tracks which local workspace the session attached to. If a thread panicked while holding the lock, the Mutex is poisoned; set_active_local_workspace deliberately refuses to set the stamp ('fail closed') rather than attach to an unverified workspace.
Source
Thrown at crates/codegen/xai-grok-pager/src/app/session_startup.rs:366
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocalWorkspaceMode {
Own,
Attach,
}
#[cfg(feature = "local-workspace")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalWorkspaceConfig {
pub mode: LocalWorkspaceMode,
pub cwd: Option<std::path::PathBuf>,
pub server_id: Option<String>,
}
#[cfg(feature = "local-workspace")]
static ACTIVE_LOCAL_WORKSPACE: std::sync::Mutex<Option<LocalWorkspaceConfig>> =
std::sync::Mutex::new(None);
#[cfg(feature = "local-workspace")]
pub fn set_active_local_workspace(cfg: Option<LocalWorkspaceConfig>) -> anyhow::Result<()> {
let mut guard = ACTIVE_LOCAL_WORKSPACE.lock().map_err(|_| {
anyhow::anyhow!("local-workspace intent mutex poisoned; refuse attach (fail closed)")
})?;
tracing::info!(
target: crate::views::welcome::workspace_mode::WORKSPACE_MODE_LOG,
event = if cfg.is_some() {
"process_stamp_set"
} else {
"process_stamp_cleared"
},
mode = cfg.as_ref().map(|c| format!("{:?}", c.mode)),
server_id = cfg.as_ref().and_then(|c| c.server_id.as_deref()),
cwd = cfg.as_ref().and_then(|c| c.cwd.as_ref().map(|p| p.display().to_string())),
"local-workspace process-wide intent stamp"
);
*guard = cfg;
Ok(())
}
#[cfg(feature = "local-workspace")]
pub fn active_local_workspace() -> anyhow::Result<Option<LocalWorkspaceConfig>> {View on GitHub (pinned to bc7f02eddd)
Solutions
- Find and fix the panic that poisoned the mutex — check logs just before this error for a panic message
- Restart the process; poisoning only lasts for the process lifetime
- In the panicking code, avoid unwrapping/logging-panic while holding the lock, or use a recovery on PoisonError if safe
- Consider replacing Mutex with a poisoning-resistant pattern (lock().unwrap_or_else(|p| p.into_inner()) only if state is provably valid)
Example fix
// before: panic while holding the lock poisons it
let mut g = ACTIVE_LOCAL_WORKSPACE.lock().unwrap();
g.replace(load_config().expect("config"));
// after: no panic inside the critical section
let cfg = load_config()?;
*ACTIVE_LOCAL_WORKSPACE.lock().map_err(|_| anyhow!("poisoned"))? = cfg; Defensive patterns
Strategy: try-catch
Validate before calling
// detect a poisoned global before setting the workspace
fn workspace_lock_healthy(m: &std::sync::Mutex<()>) -> bool {
matches!(m.try_lock(), Ok(_) | Err(std::sync::TryLockError::WouldBlock))
} Try / catch
if let Err(e) = session_startup::set_active_local_workspace(Some(cfg)) {
if e.to_string().contains("mutex poisoned") {
eprintln!("earlier panic corrupted workspace state; restart the process");
}
return Err(e);
} Prevention
- Never unwrap/panic while holding ACTIVE_LOCAL_WORKSPACE lock
- Return Results from critical sections instead of expect/unwrap
- Run catch_unwind around plugin/extension code that may panic
- On poison, restart the process rather than retrying the setter
When it happens
Trigger: Calling set_active_local_workspace after any earlier thread panicked while holding the ACTIVE_LOCAL_WORKSPACE lock (e.g. a panic inside a closure that had locked it), leaving lock() returning Err(PoisonError).
Common situations: A panic elsewhere in the app while the workspace-intent lock was held (test teardown panics, unexpected unwrap failures), followed by a session fork/welcome flow attempting to set a local workspace.
Related errors
- {LOCAL_WORKSPACE_REQUIRES_CHAT}
- invalid {GROK_CHAT_LOCAL_WORKSPACE_MODE_ENV}={other:?}; expe
- {LOCAL_WORKSPACE_ATTACH_NEEDS_SERVER_ID}
- local workspace cwd must exist and be canonicalizable: {}: {
- local workspace cwd must be an existing directory: {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/d12fb5b79b33d208.
Report an issue: GitHub.