Skip to content

WlSessionLock crashes when an active lock surface loses its output #1155

Description

@IdrisGit

When the last real Wayland output temporarily disappears while the session is locked, Quickshell exits with status 255. This occurs during suspend/resume, DPMS transitions, or external-monitor disconnection.

Hyprland remains running and the session remains locked, but the shell must be restarted.

Reproduction

  1. Use an external monitor with the laptop lid closed.
  2. Lock the session using WlSessionLock.
  3. Suspend/resume, toggle DPMS off/on, or disconnect/reconnect the monitor.
  4. During the transition, the compositor briefly has no real outputs.

Observed behavior

QtWayland creates a placeholder screen after the last output disappears. Quickshell rejects the placeholder screen, then the Wayland connection terminates:

  • qt.qpa.wayland: There are no outputs - creating placeholder screen and not creating lock surface for screen QScreen(...) as it is not backed by a valid wayland output.
  • Got removal for monitor "FALLBACK" which was not previously tracked.
  • Wayland connection experienced a fatal error: Invalid argument

Quickshell exits with status 255. The FALLBACK warning appears to be a timing marker rather than the source of the fatal Wayland request.

Expected behavior

Quickshell should remain locked and running through a temporary zero-output state, then restore its lock surface when an output returns.

Environment

Quickshell: 0.3.1-1
Qt: 6.11.2
Hyprland: 0.56.2
Aquamarine: 0.15.0
NVIDIA: 610.57.04
GPU setup: NVIDIA GPU with AMD iGPU

No report.txt was generated because the process exited through the fatal Wayland connection error rather than Quickshell's crash reporter.

Activity

  1. dpan commented on Sep 18, 2026

    @dpan

    Confirming this on a different setup: desktop, idle/wake (no suspend, no lid), single output, AMD GPU. Same log chain, same exit 255. Hyprland stayed up.

    Environment

    • Quickshell 0.3.1-1, Qt 6.11.2, Hyprland 0.56.2 (efb5099), Aquamarine 0.15.0
    • Omarchy 4.0.4 (stock omarchy.lock / WlSessionLock)
    • GPU: AMD RX 6700 XT (1002:73df) + Intel Haswell iGPU (8086:0412)
    • One output: DP-1
    • Idle: screensaver 150s, lock 300s. Lock blanks the panel 5s later via omarchy-brightness-display off (DPMS), not a lid event.

    What happened

    1. Idle lock succeeded (secure=true) at 13:01.
    2. User input at 13:23:49 (idle-monitor: active).
    3. Seven seconds later the last real output was gone. Qt made a placeholder screen, lock surfaces were skipped, Hyprland IPC saw an untracked FALLBACK removal, then the Wayland connection died (Invalid argument). Process exited 255.
    4. Watchdog relaunched the shell; Omarchy's stranded-lock recovery retook the lock (secure=true).
    5. The same zero-output / FALLBACK / fatal-error / 255 sequence repeated ~40s later on the recovered instance.
    6. After the second relaunch, PAM unlock worked. Session apps were intact (Hyprland never crashed). Hyprland's "lockscreen app died" failsafe was visible in the gap.

    Log (trimmed; twice in one wake)

    13:23:49  idle-monitor: active
    13:23:56  qt.qpa.wayland: There are no outputs - creating placeholder screen
    13:23:56  Layershell screen does not correspond to a real screen.
    13:23:56  Not creating lock surface for screen QScreen(..., name="") as it is not backed by a valid wayland output.
    13:23:56  quickshell.hyprland.ipc: Got removal for monitor "FALLBACK" which was not previously tracked.
    13:23:56  The Wayland connection experienced a fatal error: Invalid argument
    13:23:56  Omarchy shell exited with status 255; relaunching.
    13:24:02  lock-stranded: recovering
    13:24:03  secure=true
    … ~30s later, identical placeholder / empty QScreen / FALLBACK / Invalid argument / 255 …
    13:24:39  lock-stranded: recovering
    13:24:40  secure=true
    

    Agrees with the report that the FALLBACK line looks like a timing marker, not the request that kills the connection. Extra datapoint: this is not NVIDIA-specific and does not need suspend or a laptop lid — idle blank on a single output is enough, and it can fire twice on one wake while outputs are still settling.

  2. calebl commented on Sep 22, 2026

    @calebl

    Third confirmation, on hardware narrower than either report above: AMD Ryzen 7 6800U iGPU only — no discrete GPU, no Intel iGPU, one output (HDMI-A-1). Same chain, same exit 255. Omarchy 4.0.0 (omarchy-dev 4.0.0.r2155.gf2cf3ce), Quickshell 0.3.1, kernel 7.2.5-4-omarchy. Trigger is plain idle-blank DPMS — no suspend, no lid, no external monitor, matching @dpan.

    I won't repeat the log chain, which matches @dpan's exactly. Four things I can add:

    1. It is a race, not a certainty — about 23%

    Over this journal's lifetime:

    Event Count
    attempted to use dangling screen object 4296
    Got removal for monitor "FALLBACK" 1770
    The Wayland connection experienced a fatal error 406

    1770 zero-output events produced 406 fatal exits. Most zero-output transitions are survived; it seems to depend on whether lock-surface realization is in flight as the outputs tear down. That may explain why it fires twice on one wake for @dpan and not at all on other wakes.

    2. It can loop for hours, not just twice

    @dpan saw two iterations in one wake. My longest unbroken loop ran 2026-09-18 12:35:34 → 18:17:20 — 5h42m, roughly 395 crashes, at a steady 17–18s per iteration. The stranded-lock recovery re-locks, hits the same zero-output state, and exits again indefinitely. Omarchy's relaunch limiter (5 per 60s) never trips, because 17–18s is only ~3.5/minute.

    3. The _exit() path leaks file descriptors until unrelated apps break

    Because the process leaves through the fatal Wayland error rather than the crash reporter — which is why there's no report.txt, as the original report notes — no destructors run, so Process children are never reaped. Each iteration orphaned one inotifywait to systemd --user:

    $ ps -o ppid= -p $(pgrep -d, -f inotifywait) | sort | uniq -c | sort -rn
        398 1116   (systemd --user)
          1 3453442 (quickshell)   <- the live one
    

    398 orphans against 406 crashes. Each holds an inotify instance, and fs.inotify.max_user_instances is 1024 per UID — I was at roughly 463. Past that ceiling every later inotify_init1() in the session fails with EMFILE, surfacing in whatever application next asks for a watch, with nothing pointing back at the shell.

    So the blast radius is wider than a shell restart: a long enough loop degrades the whole user session. (Downstream, omacom/omarchy#11385 adds setpriv --pdeathsig TERM to contain the leak on the Omarchy side, but the fd exhaustion is a consequence of the _exit() path here.)

    4. Does afb2c27c already cover this?

    afb2c27c ("wayland/lock: guard against reentrancy during surface creation", 2026-08-25, five days after the v0.3.1 tag) looks directly relevant, and is unreleased:

    • onScreensChanged() gains a !this->realizing guard, with the comment "Theoretically screens shouldnt be invalidated while realizing, instead in later event loop cycles." On this hardware they demonstrably are.
    • A failed manager->lock() now calls unlock() and returns, instead of falling through to updateSurfaces(true, old) as 0.3.1 does.

    Is this case expected to be fixed by that commit? If so, a release would help — every reporter here is on 0.3.1 from their distro. I'm happy to build master and run the reproducer against it if that's useful; at a ~23% per-transition rate it should take only a few idle cycles to get a signal either way.

  3. AntoineArt commented on Sep 24, 2026

    @AntoineArt

    Another trigger for the same chain: laptop with three external displays on a USB-C dock, suspended while locked, dock unplugged during suspend. No DPMS or idle involved. On resume every output the lock surfaces were on is gone at once.

    Quickshell 0.3.1-1, Qt 6.11.2, Hyprland 0.56.2 (efb5099), Omarchy 4.0.4, Framework 13 (Ryzen 7040, 780M iGPU only), kernel 7.2.5. hyprmoncfgd switches to the internal-only profile on resume.

    One detail I haven't seen in the other reports: the internal panel was back for almost 6 s before the fatal error, and the lock never got a surface on it.

    16:45:22.26  kernel resume complete
    16:45:23.43  hyprland: monitorremoved DP-9, DP-10, DP-12   (the 3 dock outputs)
    16:45:23.57  hyprland: monitoradded FALLBACK
    16:45:23.62  qt.qpa.wayland: There are no outputs - creating placeholder screen
    16:45:24.94  Not creating lock surface for screen QScreen(0x…df40, name="") as it is not backed by a valid wayland output.  (x3)
    16:45:25.98  hyprmoncfgd: applied profile (eDP-1 enabled)
    16:45:26.00  hyprland: monitoradded eDP-1
    16:45:26.03  hyprland: monitorremoved FALLBACK
    16:45:26.15  Not creating lock surface for screen QScreen(0x…df40, name="") …
    16:45:27.79  Not creating lock surface for screen QScreen(0x…7430, name="") …  (x3, different QScreen)
    16:45:29.91  Not creating lock surface for screen QScreen(0x…7430, name="") …
    16:45:31.69  attempted to use dangling screen object  (x12)
    16:45:31.70  quickshell.hyprland.ipc: Got removal for monitor "FALLBACK" which was not previously tracked.
    16:45:32.10  hyprland: error in client communication (pid …)
    16:45:32.30  The Wayland connection experienced a fatal error: Invalid argument
    16:45:32.37  exited with status 255; relaunching
    16:45:34.24  lock-stranded: recovering   (new instance locks fine on eDP-1)
    

    So:

    • eDP-1 was added at 26.00, but every later "not creating lock surface" line still refers to a nameless QScreen. A second nameless QScreen (0x…7430) shows up at 27.79 and the lock skips it the same way. WlSessionLock doesn't seem to retry once a usable screen shows up.
    • The Hyprland IPC removal of FALLBACK (sent by the compositor at 26.03) only reaches Quickshell at 31.70, about 5.7 s late. During that window the shell rebuilt its per-screen panels three times. That fits the "timing marker" reading: the dangling-screen accesses and the fatal request come out of the same backlog being processed.

    In my journal (60 days) this happened only once: 8 FALLBACK removals, 13 dangling-screen warnings, 1 fatal error. The resume above is the only one where every output disappeared while locked. It left one orphaned inotifywait under systemd --user, matching @calebl's point 3.

    Locking, suspending and pulling the dock seems to be a fairly deterministic way to get a zero-output state while the lock is active, in case that helps check afb2c27c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions