{"record":{"id":"1018e71d8d0ffdd8","repo":"niri-wm/niri","slug":"no-free-x11-display-found-after-50-attempts","errorCode":null,"errorMessage":"no free X11 display found after 50 attempts","messagePattern":"no free X11 display found after 50 attempts","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/utils/xwayland/mod.rs","lineNumber":77,"sourceCode":"        (x11_tmp.st_mode & 0o1000) == 0o1000,\n        \"X11 directory is missing the sticky bit\"\n    );\n\n    Ok(())\n}\n\nfn pick_x11_display(start: u32) -> anyhow::Result<(u32, OwnedFd, Unlink)> {\n    for n in start..start + 50 {\n        let lock_path = format!(\"/tmp/.X{n}-lock\");\n        let flags = OFlags::WRONLY | OFlags::CLOEXEC | OFlags::CREATE | OFlags::EXCL;\n        let Ok(lock_fd) = rustix::fs::open(&lock_path, flags, 0o444.into()) else {\n            // FIXME: check if the target process is dead and reuse the lock.\n            continue;\n        };\n        return Ok((n, lock_fd, Unlink(lock_path)));\n    }\n\n    Err(anyhow!(\"no free X11 display found after 50 attempts\"))\n}\n\nfn bind_to_socket(addr: &SocketAddr) -> anyhow::Result<UnixListener> {\n    let listener = UnixListener::bind_addr(addr).context(\"error binding socket\")?;\n    Ok(listener)\n}\n\n#[cfg(target_os = \"linux\")]\nfn bind_to_abstract_socket(display: u32) -> anyhow::Result<UnixListener> {\n    use std::os::linux::net::SocketAddrExt;\n\n    let name = format!(\"/tmp/.X11-unix/X{display}\");\n    let addr = SocketAddr::from_abstract_name(name).unwrap();\n    bind_to_socket(&addr)\n}\n\nfn bind_to_unix_socket(display: u32) -> anyhow::Result<(UnixListener, Unlink)> {\n    let name = format!(\"/tmp/.X11-unix/X{display}\");","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/niri-wm/niri/blob/606284464d4a99bb35710fee68192bc71085ee7c/src/utils/xwayland/mod.rs#L59-L95","documentation":"When spawning Xwayland, niri picks a free X display number by trying to exclusively create the lock files /tmp/.X{n}-lock for n in start..start+50 (O_EXCL create). If all 50 attempts hit an existing lock file, it gives up with this error and Xwayland cannot start. Existing locks are usually stale — left by crashed X servers/Xwayland processes (the FIXME notes dead-process locks are not reused yet).","triggerScenarios":"50 consecutive /tmp/.X*-lock files present: many crashed Xwayland/X sessions accumulating locks in a long-lived /tmp, another X server (or several) legitimately holding lock files in that range, or /tmp not being cleaned because the system has not rebooted and processes leaked locks.","commonSituations":"Long-running machines where compositor crashes or force-killed Xwayland leave /tmp/.X1-lock .. .X50-lock behind; nested X servers (Xvfb, x11vnc, distroboxes) consuming display numbers; /tmp on tmpfs cleared only at reboot on a system that never reboots.","solutions":["Verify no live X servers hold them: 'pgrep -a \"Xwayland|Xorg|Xvfb\"', then remove stale locks: 'rm /tmp/.X[0-9]*-lock' and stale sockets 'rm /tmp/.X11-unix/X*' (only for display numbers not in use).","Kill and let niri respawn Xwayland cleanly (log out and back in) so it picks the freed display number.","Free display numbers by shutting down unneeded nested X servers (Xvfb from CI, x11vnc) that squat low display numbers.","As a last resort reboot (or remount tmpfs /tmp) to clear the lock directory entirely."],"exampleFix":"# before: 50 lock files -> \"no free X11 display found after 50 attempts\"\nls /tmp/.X*-lock\n# after: clean stale locks for dead servers and restart the session\npgrep -a Xwayland   # confirm which are alive\nrm -f /tmp/.X*-lock /tmp/.X11-unix/X*\nniri msg action do-screen-transition  # or just relaunch the app / relogin","handlingStrategy":"fallback","validationCode":"# shell: detect stale locks (dead PID) before launching Xwayland consumers\nfor f in /tmp/.X*-lock; do\n  [ -e \"$f\" ] || continue\n  pid=$(cat \"$f\")\n  if ! kill -0 \"$pid\" 2>/dev/null; then echo \"stale lock: $f (pid $pid dead)\"; fi\ndone","typeGuard":null,"tryCatchPattern":"match pick_x11_display(start) {\n    Ok(x) => x,\n    Err(_) => {\n        // fallback: sweep dead-owner lock files once, then retry the range\n        clean_stale_x11_locks();\n        pick_x11_display(start).context(\"no free X11 display even after cleaning stale locks\")?\n    }\n}","preventionTips":["Log out cleanly (not SIGKILL) so Xwayland removes its /tmp/.Xn-lock and /tmp/.X11-unix/Xn.","Periodically reap dead-owner locks: the lock file contains the owning PID — verify with kill -0 before deleting.","Do not run dozens of Xvfb/X servers with low display numbers on shared long-lived machines.","Reboot or clear tmpfs /tmp after repeated compositor crashes to reset the display-number space."],"tags":["xwayland","x11","lock-file","tmp","display-number","stale-state"],"backgroundTag":"stale-lock-file","analyzedSha":"606284464d4a99bb35710fee68192bc71085ee7c","analyzedAt":"2026-08-16T21:48:00.228Z","schemaVersion":2},"datasetVersion":"2026-08-18T00:17:09.346Z"}