bevyengine/bevy · critical
Failed to build event loop
Error message
Failed to build event loop
What it means
During startup bevy_winit builds the platform event loop with winit's EventLoopBuilder and .expect("Failed to build event loop") — any winit failure here aborts the process before the app runs. winit fails when it cannot connect to a display server (X11/Wayland), platform libraries are missing, or platform integration is misconfigured (e.g. Android without the app handle set up by #[bevy_main]).
Source
Thrown at crates/bevy_winit/src/lib.rs:137
}
#[cfg(target_os = "windows")]
{
use winit::platform::windows::EventLoopBuilderExtWindows;
event_loop_builder.with_any_thread(self.run_on_any_thread);
}
#[cfg(target_os = "android")]
{
use winit::platform::android::EventLoopBuilderExtAndroid;
let msg = "Bevy must be setup with the #[bevy_main] macro on Android";
event_loop_builder
.with_android_app(bevy_android::ANDROID_APP.get().expect(msg).clone());
}
let event_loop = event_loop_builder
.build()
.expect("Failed to build event loop");
let event_loop_proxy = event_loop.create_proxy();
// Wake up the event loop when `Ctrl+C` is received so that the app can
// exit even while idle in a reactive update mode
#[cfg(any(all(unix, not(target_os = "horizon")), windows))]
{
let event_loop_proxy = event_loop_proxy.clone();
bevy_app::TerminalCtrlCHandlerPlugin::register_exit_handler(move || {
let _ = event_loop_proxy.send_event(WinitUserEvent::WakeUp);
});
}
app.init_resource::<WinitMonitors>()
.init_resource::<WinitSettings>()
.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle()))
.insert_resource(EventLoopProxyWrapper(event_loop_proxy))
.add_message::<RawWinitWindowEvent>()View on GitHub (pinned to 396ca72708)
Solutions
- Run inside a graphical session, or forward one over SSH (ssh -X) / WSLg on Windows.
- On headless CI, use a virtual framebuffer: xvfb-run ./your_app, or install the X11/Wayland client libraries (libxkbcommon, libx11, libwayland) if they are missing.
- For genuinely headless workloads, build without the windowing plugins or configure WindowPlugin with primary_window: None and use a headless renderer setup.
- On Android, enter via #[bevy_main] so the android app handle is available to the event loop builder.
- Check backend environment variables (e.g. WGPU_BACKEND, WINIT_UNIX_BACKEND) for values unsupported on the machine.
Example fix
# before (headless server / CI) ./my_bevy_game # panics: Failed to build event loop # after xvfb-run -a ./my_bevy_game # virtual display for windowed apps on headless machines
Defensive patterns
Strategy: validation
Validate before calling
#[cfg(target_os = "linux")]
fn display_available() -> bool {
std::env::var("DISPLAY").is_ok() || std::env::var("WAYLAND_DISPLAY").is_ok()
}
#[cfg(target_os = "linux")]
fn main() {
if !display_available() {
eprintln!("no display server found; set DISPLAY/WAYLAND_DISPLAY or run under xvfb-run");
std::process::exit(1);
}
my_game::run();
} Try / catch
// Startup abort, not a Result: validate the environment before App::new().run(). // In CI, choose headless plugins or wrap execution with `xvfb-run` instead of catching.
Prevention
- Check DISPLAY/WAYLAND_DISPLAY (Linux) before launching windowed builds.
- Keep a separate headless/CI feature set that excludes bevy_winit windowing.
- Install X11/Wayland client libs in minimal containers that must run windowed apps.
- On Android always use #[bevy_main].
When it happens
Trigger: Running a windowed Bevy app on a headless Linux machine or CI runner with no $DISPLAY/WAYLAND_DISPLAY; SSH sessions without X forwarding; missing libxkbcommon/X11/Wayland client libraries; Android builds not using #[bevy_main]; sandboxed environments denying display sockets.
Common situations: CI pipelines launching integration tests that accidentally include DefaultPlugins; deploying a game binary to a server; minimal container images without graphics runtime libs; fresh Linux installs without mesa/X11; WSL without WSLg.
Related errors
- window.canvas() can only be called in main thread.
- Cannot query for canvas element.
- Cannot find element: {selector}.
- Color compression flag must be `COMPRESS_COLOR_FLOAT16` or `
- Field name should exist
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/8b88cdd084df42b3.
Report an issue: GitHub.