libnyanpasu/clash-nyanpasu · error
failed to retain window
Error message
failed to retain window
What it means
After obtaining the raw NSWindow pointer, the code re-anchors it into an objc2 `Retained<NSWindow>` via `retain_autoreleased`. The expect fires when the pointer is not a valid autoreleased `NSWindow` (nil, already deallocated, or wrong type), so objc2 refuses to build a safe reference. It indicates the window handle became invalid between the `ns_window()` call and the retain, or the unsafe cast is wrong.
Source
Thrown at backend/tauri/src/window.rs:969
#[derive(Debug, Clone)]
struct WindowState {
window: WebviewWindow<tauri::Wry>,
traffic_lights_pos: Position,
}
impl WindowState {
fn new(window: WebviewWindow<tauri::Wry>, traffic_lights_pos: Position) -> Self {
Self {
window,
traffic_lights_pos,
}
}
fn with_ns_window<T>(&self, func: impl FnOnce(Retained<NSWindow>) -> T) -> T {
let ns_window = self.window.ns_window().expect("window not found");
let ns_window = unsafe { Retained::retain_autoreleased(ns_window as *mut NSWindow) }
.expect("failed to retain window");
func(ns_window)
}
fn apply_traffic_lights_pos(&self) {
self.with_ns_window(|win| {
set_traffic_lights_pos(win, self.traffic_lights_pos)
.expect("failed to set traffic lights pos");
});
}
}
#[derive(Debug)]
struct TrafficLightsWindowDelegateIvars {
app_box: WindowState,
super_class: Retained<ProtocolObject<dyn NSWindowDelegate>>,
}
const WINDOW_DID_ENTER_FULL_SCREEN: &str = "internal:://window-did-enter-full-screen";View on GitHub (pinned to f7dbce2997)
Solutions
- Ensure the call runs on the main thread and the window is confirmed alive before the retain.
- Check `retain_autoreleased` returns Some and bail gracefully (log + skip) instead of expecting.
- Pin/align tauri and objc2 crate versions so `ns_window()`'s autoreleased contract matches the code's assumption.
- Move traffic-light updates out of close-time callbacks where the window may already be deallocating.
Example fix
// before
let ns_window = unsafe { Retained::retain_autoreleased(ns_window as *mut NSWindow) }
.expect("failed to retain window");
// after
let Some(ns_window) = (unsafe {
Retained::retain_autoreleased(ns_window as *mut NSWindow)
}) else {
tracing::warn!("could not retain NSWindow; skipping");
return Default::default();
}; Defensive patterns
Strategy: fallback
Validate before calling
// run only on main thread with a live window
let mtm = MainThreadMarker::new().expect("must run on main thread"); Type guard
fn retainable_ns_window(ptr: *mut NSWindow) -> Option<Retained<NSWindow>> {
unsafe { Retained::retain_autoreleased(ptr) }
} Try / catch
let Some(win) = (unsafe { Retained::retain_autoreleased(p as *mut NSWindow) }) else {
tracing::warn!("retain_autoreleased failed; skipping traffic lights");
return Default::default();
}; Prevention
- Align tauri/objc2 versions so ns_window() guarantees an autoreleased pointer
- Avoid touching the NSWindow during deallocation/close callbacks
- Always call via main-thread-only helpers
When it happens
Trigger: Calling `with_ns_window` while the NSWindow is being torn down (close callback racing deallocation), or the `ns_window()` return being a non-NSWindow pointer so the `*mut NSWindow` cast is invalid.
Common situations: App shutdown or window close triggering `apply_traffic_lights_pos` on a half-deallocated window; calling from a non-main thread where AppKit objects are invalid; Tauri/objc2 version mismatch changing raw-handle semantics.
Related errors
- failed to set traffic lights pos
- failed to get delegate
- URL event received before prepare() was called
- listen() called before prepare()
- prepare() called more than once with different identifiers.
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/7190eae7243c3339.
Report an issue: GitHub.