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

  1. Ensure the call runs on the main thread and the window is confirmed alive before the retain.
  2. Check `retain_autoreleased` returns Some and bail gracefully (log + skip) instead of expecting.
  3. Pin/align tauri and objc2 crate versions so `ns_window()`'s autoreleased contract matches the code's assumption.
  4. 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

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/7190eae7243c3339. Report an issue: GitHub.