OpenCut-app/OpenCut · critical

failed to open the main window

Error message

failed to open the main window

What it means

This is a Rust panic raised by .expect("failed to open the main window") on the Result returned by GPUI 0.2.2's App::open_window (apps/desktop/src/main.rs:36-56). open_window creates the OS-level window and initializes a GPU-backed compositor surface for it; the returned Result is Err only when that platform/graphics initialization fails, so the panic message is the app author's own string, not a GPUI error text. The underlying cause is whatever the platform window+graphics backend reported (no display server, an incompatible Wayland compositor, a GPU/Vulkan/GL init failure, or a permission error such as running as root on Wayland). Because the main window is the application's only entry surface, this panic aborts the process at startup.

Source

Thrown at apps/desktop/src/main.rs:56

                titlebar: Some(TitlebarOptions {
                    title: Some(SharedString::from("OpenCut")),
                    ..Default::default()
                }),
                window_bounds: Some(WindowBounds::Maximized(bounds)),
                ..Default::default()
            },
            |window, cx| {
                cx.new(|cx| {
                    cx.observe_window_appearance(window, |_, window, _| {
                        window.refresh();
                    })
                    .detach();

                    Shell::new(cx)
                })
            },
        )
        .expect("failed to open the main window");
    });
}

View on GitHub (pinned to 400f097bec)

Solutions

  1. Run the binary inside a real graphical session: on Linux export DISPLAY=:0 for X11 or log into a Wayland session, on Windows/macOS just launch from the desktop; over SSH use ssh -X/-Y or a Wayland portal.
  2. If on WSL and the panic still occurs, confirm the guard at main.rs:23-31 is firing: check /proc/sys/kernel/osrelease contains 'microsoft', that both DISPLAY and WAYLAND_DISPLAY are set before launch (the guard needs both), and that WAYLAND_DISPLAY is unset afterwards; if only one is set, the guard is skipped and you must `unset WAYLAND_DISPLAY` (or remove it from the environment) manually so GPUI falls back to X11/XWayland.
  3. Surface the real cause instead of the opaque string: replace .expect("...") with a match on the Result and print or log the inner GPUI error (e.g. `match cx.open_window(...) { Ok(_) => {}, Err(e) => { eprintln!("open_window failed: {e:?}"); std::process::exit(1); } }`) so the underlying graphics/protocol error is visible.
  4. For headless or CI execution where no real display exists, run under a virtual framebuffer: `xvfb-run -a -s "-screen 0 1280x800x24" ./opencut-desktop` or set up wlroots/swcweston as a nested Wayland compositor so GPUI has a windowing system to bind to.
  5. Install the platform graphics dependencies GPUI's renderer links against: on Linux add libwayland, libxkbcommon, vulkan-loader, mesa-dri/GPU drivers and the xdg_wm_base-providing compositor; verify with `vulkaninfo` / `glxinfo | grep 'OpenGL renderer'`; on macOS confirm a Metal device exists, on Windows confirm D3D11+ is available.
  6. Do not run the GUI binary as root/sudo under Wayland — compositors reject root clients; run as the session user, or switch the session to X11 where root is tolerated.
  7. Pin or align GPUI to a version compatible with the target compositor; if the host cannot be upgraded past xdg_wm_base v1 (older WSLg), keep the WAYLAND_DISPLAY-stripping workaround and ensure it runs before any GPUI thread spawns, as the comment at main.rs:24-27 requires.

Example fix

// before (apps/desktop/src/main.rs:36-56) — opaque panic, no diagnostic:
        cx.open_window(
            WindowOptions { /* ... */ },
            |window, cx| {
                cx.new(|cx| { /* ... */ Shell::new(cx) })
            },
        )
        .expect("failed to open the main window");

// after — surface the real platform/graphics error and exit cleanly:
        if let Err(e) = cx.open_window(
            WindowOptions { /* ... */ },
            |window, cx| {
                cx.new(|cx| {
                    cx.observe_window_appearance(window, |_, window, _| window.refresh())
                        .detach();
                    Shell::new(cx)
                })
            },
        ) {
            eprintln!(
                "failed to open the main window: {e:?}\n\
                 hint: ensure a display server is available (DISPLAY or WAYLAND_DISPLAY), \n\
                 GPU/Vulkan drivers are installed, and you are not running headless or as root on Wayland."
            );
            cx.quit();
        }
Defensive patterns

Strategy: try-catch

Validate before calling

// Run BEFORE Application::run so you fail fast with a clear message
// instead of letting GPUI panic deep in window creation.
#[cfg(target_os = "linux")]
fn ensure_display_available() {
    let has_x11 = std::env::var_os("DISPLAY").is_some_and(|d| !d.is_empty());
    let has_wayland = std::env::var_os("WAYLAND_DISPLAY").is_some_and(|d| !d.is_empty());
    if !has_x11 && !has_wayland {
        eprintln!("no display server found: set DISPLAY (X11) or WAYLAND_DISPLAY, \n                   use ssh -X/-Y, or run under xvfb-run.");
        std::process::exit(1);
    }
    if std::id::equals(0) && has_wayland && !has_x11 {
        eprintln!("running as root under Wayland is rejected by most compositors; \n                   run as the session user or switch to an X11 session.");
        std::process::exit(1);
    }
}

fn main() {
    #[cfg(target_os = "linux")] ensure_display_available();
    Application::new().run(|cx: &mut App| { /* ... open_window ... */ });
}

Type guard

// GPUI's open_window returns Result<Window, gpui::Errno> (platform-specific Error).
// There is no narrower type to guard on, so narrow by case on the Result itself:
fn try_open_main_window(cx: &mut gpui::App, opts: gpui::WindowOptions) -> bool {
    match cx.open_window(opts, |window, cx| {
        cx.new(|cx| { /* build Shell::new(cx) */ shell::Shell::new(cx) })
    }) {
        Ok(_) => true,
        Err(err) => {
            eprintln!("open_window rejected: {err:?}");
            false
        }
    }
}

Try / catch

// Inside Application::run — replace .expect() with explicit error handling.
// GPUI does not throw; it returns Result, so the Rust idiom is match / if let Err.
Application::new().run(|cx: &mut App| {
    let result = cx.open_window(WindowOptions { /* ... */ }, |window, cx| {
        cx.new(|cx| {
            cx.observe_window_appearance(window, |_, w, _| w.refresh()).detach();
            Shell::new(cx)
        })
    });
    if let Err(err) = result {
        tracing::error!(?err, "failed to open the main window");
        eprintln!("failed to open the main window: {err:?}");
        // Stop the app cleanly instead of panicking.
        cx.quit();
    }
});

Prevention

When it happens

Trigger: Running the binary where App::open_window cannot obtain a usable window: (1) on Linux with neither DISPLAY nor WAYLAND_DISPLAY set, e.g. a plain SSH session or a systemd service/cron job with no graphical session; (2) on a Wayland compositor whose xdg_wm_base protocol is older than v2 — exactly the WSLg case the main.rs:23-31 block tries to neutralize, which still fails if the guard does not fire (e.g. is_wsl false, or only one of the two env vars set, or the unsafe removal racing with GPUI's own env read); (3) a GPU/graphics backend init failure under GPUI's Blade renderer (missing Vulkan loader, broken MESA/DRI, no suitable GPU device); (4) running the binary as root on Wayland, where most compositors refuse the connection; (5) Windows/macOS graphics-API init failure (no Metal device, locked D3D context). In every case open_window returns Err and .expect() turns it into this panic.

Common situations: Developers hit this most often when launching the desktop binary outside a real GUI session: over SSH without X11/Wayland forwarding, from a CI container with no display, as a systemd/launchd service, or inside a Docker/Podman image that did not install libwayland/libxkbcommon/Vulkan/OpenGL drivers. The second most common context is a version change — bumping GPUI past a point where it tightened Wayland protocol requirements (the code's own comment ties this to GPUI 0.2.2 vs WSLg's xdg_wm_base v1), or moving a binary built against a newer graphics stack onto an older host. A third context is the WSL guard at lines 23-31 silently not applying (DISPLAY and WAYLAND_DISPLAY not both set, or osrelease detection failing in an unpacked WSL rootfs), leaving the broken Wayland path active. The panic string itself carries no diagnostic, so teams often misread it as a logic bug in Shell::new rather than a window-creation failure.


AI-assisted analysis of OpenCut-app/OpenCut@400f097bec (2026-08-12). Data as JSON: /api/errors/5283c0b3f9fe7c8c. Report an issue: GitHub.