tauri-apps/tauri · error

poisoned window

Error message

poisoned window

What it means

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.

Source

Thrown at crates/tauri/src/window/mod.rs:1256

  });
```
  "####
  )]
  pub fn on_menu_event<F: Fn(&Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(
    &self,
    f: F,
  ) {
    self
      .manager
      .menu
      .event_listeners
      .lock()
      .unwrap()
      .insert(self.label().to_string(), Box::new(f));
  }

  pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option<WindowMenu<R>>> {
    self.menu.lock().expect("poisoned window")
  }

  #[cfg_attr(target_os = "macos", allow(dead_code))]
  pub(crate) fn has_app_wide_menu(&self) -> bool {
    self
      .menu_lock()
      .as_ref()
      .map(|m| m.is_app_wide)
      .unwrap_or(false)
  }

  #[cfg_attr(target_os = "macos", allow(dead_code))]
  pub(crate) fn is_menu_in_use<I: PartialEq<MenuId>>(&self, id: &I) -> bool {
    self
      .menu_lock()
      .as_ref()
      .map(|m| id.eq(m.menu.id()))
      .unwrap_or(false)

View on GitHub (pinned to 52e4b6e71d)

Solutions

  1. Run with RUST_BACKTRACE=1 and identify the FIRST panic — it happened while the menu lock was held and is the real bug.
  2. Remove unwrap/expect from all menu-related code: menu building, find_by_id/get lookups, and on_menu_event handlers.
  3. 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.
  4. Re-create the Window after fixing — the poisoned menu mutex is unrecoverable for the process.

Example fix

// before: unwrap in a menu handler can panic while menu machinery runs;
// the panic poisons the window menu mutex, later menu ops fail
window.on_menu_event(|w, e| {
  let item = w.menu().unwrap().get(e.id()).unwrap().as_menuitem().unwrap();
  item.set_enabled(false).unwrap();
});

// after: every lookup is fallible, nothing panics
window.on_menu_event(|w, e| {
  let Some(menu) = w.menu() else { return };
  if let Some(item) = menu.get(e.id()).and_then(|i| i.as_menuitem()) {
    if let Err(err) = item.set_enabled(false) {
      log::error!("failed to disable menu item: {err}");
    }
  }
});
Defensive patterns

Strategy: try-catch

Try / catch

use std::panic::{catch_unwind, AssertUnwindSafe};

// menu_lock() panics once the per-window menu mutex is poisoned;
// wrap menu operations so the UI can degrade instead of crashing
let outcome = catch_unwind(AssertUnwindSafe(|| {
  window.set_menu(Some(menu))
}));
if outcome.is_err() {
  log::error!("window menu mutex poisoned — menu disabled for this window");
}

Prevention

When it happens

Trigger: 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.

Common situations: 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'.

Related errors


AI-assisted analysis of tauri-apps/tauri@52e4b6e71d (2026-08-20). Data as JSON: /api/errors/604ddc37a315b9e0. Report an issue: GitHub.