Linux/WSL desktop window controls missing or broken

NousResearch/hermes-agent · cluster 53514 · apps/desktop

Data as of

Nothing open on GitHub. Fix merged in #53989. Check it covers your case.

The short answer

0open issues
0open PRs
11items grouped
Known?
Yes. 11 related items grouped here, 4 issues already closed.
Fix event
A fix was merged to main in PR #53989. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #113247. That is separate from the fix event.
Still open
Nothing open on GitHub.

Multiple bug reports and fix attempts addressing missing or broken native window controls (minimize, maximize, close) on Linux desktop environments including Wayland, WSLg, X11/XWayland, and with titleBarStyle hidden or overlay configurations. The work spans Electron 40+ compatibility, renderer-drawn fallbacks, and related titlebar layout concerns.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #53989 fix(desktop): restore window controls on plain Linux (min/max/close)

    merged to main

    files: apps/desktop/electron/main.cjs, apps/desktop/electron/titlebar-overlay-width.cjs, apps/desktop/electron/titlebar-overlay-width.test.cjs, scripts/release.py

    Show the diff
    • apps/desktop/electron/main.cjs
      @@ -534,9 +534,10 @@ function getTitleBarOverlayOptions() {
           return { height: TITLEBAR_HEIGHT }
         }
       
      -  // Windows + WSLg paint WCO natively; plain Linux disables it (frameless hidden
      -  // titlebar still applies).
      -  if (!IS_WINDOWS && !IS_WSL) {
      +  // WSLg paints WCO via the RDP host's own min/max/close, so requesting
      +  // an Electron overlay there just leaves a dead gap. Plain Linux (KDE,
      +  // GNOME) can use the native overlay — let it through.
      +  if (!IS_WINDOWS && IS_WSL) {
           return false
         }
       
      @@ -3788,7 +3789,7 @@ function getWindowButtonPosition() {
       }
       
       function getNativeOverlayWidth() {
      -  return computeNativeOverlayWidth({ isWindows: IS_WINDOWS, isWsl: IS_WSL })
      +  return computeNativeOverlayWidth({ isWindows: IS_WINDOWS, isWsl: IS_WSL, isMac: IS_MAC })
       }
       
       function getWindowState() {
    • apps/desktop/electron/titlebar-overlay-width.cjs
      @@ -1,11 +1,24 @@
      -// Pre-layout fallback for WCO right-edge reservation (--titlebar-tools-right).
      -// Live width comes from navigator.windowControlsOverlay in the renderer.
      +'use strict'
       
       const OVERLAY_FALLBACK_WIDTH = 144
       
      -/** @param {{ isWindows?: boolean, isWsl?: boolean }} opts */
      -function nativeOverlayWidth({ isWindows = false, isWsl = false } = {}) {
      -  return isWindows || isWsl ? OVERLAY_FALLBACK_WIDTH : 0
      +/**
      + * Static pre-layout reservation (px) for the right-side native window-controls
      + * overlay (min/max/close). Only a FALLBACK — once laid out the renderer reads
      + * the exact width from navigator.windowControlsOverlay
      + * (use-window-controls-overlay-width.ts) and uses this value only when the WCO
      + * API is unavailable.
      + *
      + * macOS uses traffic lights positioned via trafficLightPosition, not a WCO
      + * overlay, so it reserves nothing here. Every other desktop platform now paints
      + * the Electron overlay (Windows, WSLg, and plain Linux KDE/GNOME), so they all
      + * reserve the fallback width.
      + *
      + * @param {{ isWindows?: boolean, isWsl?: boolean, isMac?: boolean }} opts
      + */
      +function nativeOverlayWidth({ isWindows = false, isWsl = false, isMac = false } = {}) {
      +  if (isMac) return 0
      +  return OVERLAY_FALLBACK_WIDTH
       }
       
       module.exports = { OVERLAY_FALLBACK_WIDTH, nativeOverlayWidth }
    • apps/desktop/electron/titlebar-overlay-width.test.cjs
      @@ -18,10 +18,17 @@ test('WSLg paints the same WCO, so it reserves the same fallback width', () => {
         assert.equal(nativeOverlayWidth({ isWsl: true }), OVERLAY_FALLBACK_WIDTH)
       })
       
      -test('plain Linux and macOS reserve nothing', () => {
      -  assert.equal(nativeOverlayWidth({ isWindows: false, isWsl: false }), 0)
      -  assert.equal(nativeOverlayWidth(), 0)
      -  assert.equal(nativeOverlayWidth({}), 0)
      +test('plain Linux paints the WCO too, so it reserves the fallback width', () => {
      +  // Regression #53185: re-enabling the overlay on plain Linux (KDE/GNOME)
      +  // without reserving its width left the native min/max/close buttons painting
      +  // on top of the app's right-edge titlebar tools.
      +  assert.equal(nativeOverlayWidth({ isWindows: false, isWsl: false }), OVERLAY_FALLBACK_WIDTH)
      +  assert.equal(nativeOverlayWidth(), OVERLAY_FALLBACK_WIDTH)
      +  assert.equal(nativeOverlayWidth({}), OVERLAY_FALLBACK_WIDTH)
      +})
      +
      +test('macOS uses traffic lights, not a WCO overlay, so it reserves nothing', () => {
      +  assert.equal(nativeOverlayWidth({ isMac: true }), 0)
       })
       
       test('the fallback width is a sane positive pixel value', () => {
    • scripts/release.py
      @@ -70,6 +70,7 @@
           "[redacted:email]": "uperLu",  # PR #50958 salvage (rename plugins/cron → plugins/cron_providers; #50872)
           "[redacted:email]": "yusekiotacode",  # PR #48706 salvage (anthropic OAuth login token endpoint → platform.claude.com; #45250/#49821)
           "[redacted:email]": "s010mn",  # PR #29221 salvage (ollama-cloud reasoning_effort xhigh→max)
      +    "[redacted:email]": "chriswesley4",  # PR #53185 salvage (re-enable titleBarOverlay on plain Linux; missing min/max/close regression)
           "[redacted:email]": "jeeves-assistant",  # PR #50771 (computer-use CuaDriver vision capture routing)
           "[redacted:email]": "ScotterMonk",  # PR #50145 salvage (cron output truncation: adapter-aware chunking, #50126)
           "[redacted:email]": "TutkuEroglu",  # PR #50481 salvage (AGENTS.md stale token-lock adapter path)
  • #113247 fix(desktop): working WSLg window controls and native Wayland launch

    merged to main

    files: apps/desktop/electron/entry.ts, apps/desktop/electron/main.ts, apps/desktop/electron/preload.ts, apps/desktop/electron/titlebar-overlay-width.test.ts, apps/desktop/electron/titlebar-overlay-width.ts, apps/desktop/electron/window-chrome-events.test.ts, apps/desktop/electron/window-chrome-events.ts, apps/desktop/electron/window-controls.test.ts, apps/desktop/electron/window-controls.ts, apps/desktop/electron/wslg-launch-process.test.ts, apps/desktop/electron/wslg-launch-process.ts, apps/desktop/electron/wslg-launch.test.ts, apps/desktop/electron/wslg-launch.ts, apps/desktop/scripts/bundle-electron-main.mjs, apps/desktop/src/app/contrib/wiring.tsx, apps/desktop/src/app/shell/wslg-window-controls.test.tsx, apps/desktop/src/app/shell/wslg-window-controls.tsx, apps/desktop/src/global.d.ts, website/docs/user-guide/desktop.md

5 PRs closed without merging

From the close comments: superseded by another PR 4 · done elsewhere 1

  • #53326 fix(desktop): enable titleBarOverlay on Linux for Electron 40+ · superseded by another PR → #53989
  • #53528 fix(desktop): add Linux close fallback when no titlebar overlay · done elsewhere → 97640fd, #53514, #53326
  • #64505 fix(desktop): renderer-drawn WSLg window controls, zoom-restore coordinator, work-area snap · superseded by another PR → #70803
  • #70803 fix(desktop): WSLg window controls + GPU crash-loop fallback · superseded by another PR → #113247
  • #71754 fix(desktop): request window controls overlay on WSLg (#57614) · superseded by another PR → #113247
Closed issues · 4
  • Closed#53514[Bug]: Linux desktop: no close button when titleBarStyle is hidden
  • Closed#53690Linux (X11/XWayland): Electron 40+ window frame missing with titleBarStyle: "hidden"
  • Closed#54294[Bug]: Desktop (Linux/Wayland): Native window controls (minimize/maximize/close) are missing
  • Closed#57614[Bug] Desktop (WSL/WSLg): no minimize/maximize/close buttons (companion to #53514)
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.18: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
apps/desktop
Merged PRs
#53989, #113247
Closed issues
4
Attempts
5 PRs closed without merging
Fix link checked
17 Sep 2026

For your agent

The same facts, typed.