{"record":{"id":"d12fb5b79b33d208","repo":"xai-org/grok-build","slug":"local-workspace-intent-mutex-poisoned-refuse-atta","errorCode":null,"errorMessage":"local-workspace intent mutex poisoned; refuse attach (fail closed)","messagePattern":"local-workspace intent mutex poisoned; refuse attach \\(fail closed\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-pager/src/app/session_startup.rs","lineNumber":366,"sourceCode":"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum LocalWorkspaceMode {\n    Own,\n    Attach,\n}\n#[cfg(feature = \"local-workspace\")]\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct LocalWorkspaceConfig {\n    pub mode: LocalWorkspaceMode,\n    pub cwd: Option<std::path::PathBuf>,\n    pub server_id: Option<String>,\n}\n#[cfg(feature = \"local-workspace\")]\nstatic ACTIVE_LOCAL_WORKSPACE: std::sync::Mutex<Option<LocalWorkspaceConfig>> =\n    std::sync::Mutex::new(None);\n#[cfg(feature = \"local-workspace\")]\npub fn set_active_local_workspace(cfg: Option<LocalWorkspaceConfig>) -> anyhow::Result<()> {\n    let mut guard = ACTIVE_LOCAL_WORKSPACE.lock().map_err(|_| {\n        anyhow::anyhow!(\"local-workspace intent mutex poisoned; refuse attach (fail closed)\")\n    })?;\n    tracing::info!(\n        target: crate::views::welcome::workspace_mode::WORKSPACE_MODE_LOG,\n        event = if cfg.is_some() {\n            \"process_stamp_set\"\n        } else {\n            \"process_stamp_cleared\"\n        },\n        mode = cfg.as_ref().map(|c| format!(\"{:?}\", c.mode)),\n        server_id = cfg.as_ref().and_then(|c| c.server_id.as_deref()),\n        cwd = cfg.as_ref().and_then(|c| c.cwd.as_ref().map(|p| p.display().to_string())),\n        \"local-workspace process-wide intent stamp\"\n    );\n    *guard = cfg;\n    Ok(())\n}\n#[cfg(feature = \"local-workspace\")]\npub fn active_local_workspace() -> anyhow::Result<Option<LocalWorkspaceConfig>> {","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager/src/app/session_startup.rs#L348-L384","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":"// before: panic while holding the lock poisons it\nlet mut g = ACTIVE_LOCAL_WORKSPACE.lock().unwrap();\ng.replace(load_config().expect(\"config\"));\n// after: no panic inside the critical section\nlet cfg = load_config()?;\n*ACTIVE_LOCAL_WORKSPACE.lock().map_err(|_| anyhow!(\"poisoned\"))? = cfg;","handlingStrategy":"try-catch","validationCode":"// detect a poisoned global before setting the workspace\nfn workspace_lock_healthy(m: &std::sync::Mutex<()>) -> bool {\n    matches!(m.try_lock(), Ok(_) | Err(std::sync::TryLockError::WouldBlock))\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = session_startup::set_active_local_workspace(Some(cfg)) {\n    if e.to_string().contains(\"mutex poisoned\") {\n        eprintln!(\"earlier panic corrupted workspace state; restart the process\");\n    }\n    return Err(e);\n}","preventionTips":["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"],"tags":["concurrency","mutex-poisoned","local-workspace"],"backgroundTag":"mutex-poisoned","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}