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.

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.

Appendix: 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)