{"record":{"id":"604ddc37a315b9e0","repo":"tauri-apps/tauri","slug":"poisoned-window","errorCode":null,"errorMessage":"poisoned window","messagePattern":"poisoned window","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tauri/src/window/mod.rs","lineNumber":1256,"sourceCode":"  });\n```\n  \"####\n  )]\n  pub fn on_menu_event<F: Fn(&Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(\n    &self,\n    f: F,\n  ) {\n    self\n      .manager\n      .menu\n      .event_listeners\n      .lock()\n      .unwrap()\n      .insert(self.label().to_string(), Box::new(f));\n  }\n\n  pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option<WindowMenu<R>>> {\n    self.menu.lock().expect(\"poisoned window\")\n  }\n\n  #[cfg_attr(target_os = \"macos\", allow(dead_code))]\n  pub(crate) fn has_app_wide_menu(&self) -> bool {\n    self\n      .menu_lock()\n      .as_ref()\n      .map(|m| m.is_app_wide)\n      .unwrap_or(false)\n  }\n\n  #[cfg_attr(target_os = \"macos\", allow(dead_code))]\n  pub(crate) fn is_menu_in_use<I: PartialEq<MenuId>>(&self, id: &I) -> bool {\n    self\n      .menu_lock()\n      .as_ref()\n      .map(|m| id.eq(m.menu.id()))\n      .unwrap_or(false)","sourceCodeStart":1238,"sourceCodeEnd":1274,"githubUrl":"https://github.com/tauri-apps/tauri/blob/52e4b6e71d8632a7e648f866c442e287ecddee34/crates/tauri/src/window/mod.rs#L1238-L1274","documentation":"Window::menu_lock() returns the per-window Mutex<Option<WindowMenu>> used by all menu operations — set_menu, remove_menu, hide_menu, show_menu, is_menu_visible and menu-event dispatch (crates/tauri/src/window/mod.rs:1320-1439). If any thread panics while that mutex is held, Rust poisons it, and the .expect(\"poisoned window\") panics on the next menu operation for that Window. As with all poisoning, this message is the second failure; the root cause is an earlier panic inside a menu code path.","triggerScenarios":"A panic while the per-window menu mutex is held: e.g. a panic during window.set_menu(...) (the WindowMenu is constructed inside the lock guard), a re-entrant set_menu/popup from inside on_menu_event that panics, native muda menu code panicking on the main thread, or panicking code run from hide_menu/show_menu dispatch closures. The 'poisoned window' expect then fires on any later menu() / set_menu(None) / hide_menu() / popup call on that window.","commonSituations":"Apps that rebuild menus dynamically (enable/disable items looked up by id with unwrap) and panic in a handler; swapping menus from inside menu event handlers; panics during context-menu popup on Linux/Windows; after the first panic, every menu interaction on that window crashes with 'poisoned window'.","solutions":["Run with RUST_BACKTRACE=1 and identify the FIRST panic — it happened while the menu lock was held and is the real bug.","Remove unwrap/expect from all menu-related code: menu building, find_by_id/get lookups, and on_menu_event handlers.","Avoid re-entrant menu mutation: don't call set_menu from inside on_menu_event; defer via app.handle() + run_on_main_thread or spawn.","Re-create the Window after fixing — the poisoned menu mutex is unrecoverable for the process."],"exampleFix":"// before: unwrap in a menu handler can panic while menu machinery runs;\n// the panic poisons the window menu mutex, later menu ops fail\nwindow.on_menu_event(|w, e| {\n  let item = w.menu().unwrap().get(e.id()).unwrap().as_menuitem().unwrap();\n  item.set_enabled(false).unwrap();\n});\n\n// after: every lookup is fallible, nothing panics\nwindow.on_menu_event(|w, e| {\n  let Some(menu) = w.menu() else { return };\n  if let Some(item) = menu.get(e.id()).and_then(|i| i.as_menuitem()) {\n    if let Err(err) = item.set_enabled(false) {\n      log::error!(\"failed to disable menu item: {err}\");\n    }\n  }\n});","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"use std::panic::{catch_unwind, AssertUnwindSafe};\n\n// menu_lock() panics once the per-window menu mutex is poisoned;\n// wrap menu operations so the UI can degrade instead of crashing\nlet outcome = catch_unwind(AssertUnwindSafe(|| {\n  window.set_menu(Some(menu))\n}));\nif outcome.is_err() {\n  log::error!(\"window menu mutex poisoned — menu disabled for this window\");\n}","preventionTips":["Treat all menu lookups (menu(), get(id), as_menuitem()) as fallible — no unwrap in on_menu_event handlers.","Do not mutate the window menu re-entrantly from inside menu event handlers; defer with run_on_main_thread or spawn.","Install a panic hook to capture the first panic — it identifies the code that actually poisoned the menu mutex.","Validate menu item ids and native menu state before set_menu to avoid panics inside the locked section."],"tags":["rust","mutex","poisoning","panic","menu","window"],"backgroundTag":"mutex-poisoning","analyzedSha":"52e4b6e71d8632a7e648f866c442e287ecddee34","analyzedAt":"2026-08-20T13:59:20.734Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}