Skip to content

fix(desktop): detect QEMU/UTM virtualized GPUs, disable HW acceleration - #65087

Open
nickmimic wants to merge 3 commits into
NousResearch:mainfrom
nickmimic:patch-1
Open

fix(desktop): detect QEMU/UTM virtualized GPUs, disable HW acceleration#65087
nickmimic wants to merge 3 commits into
NousResearch:mainfrom
nickmimic:patch-1

Conversation

@nickmimic

@nickmimic nickmimic commented Jul 15, 2026

Copy link
Copy Markdown

What does this PR do?

Extends detectRemoteDisplay() in apps/desktop/electron/bootstrap-platform.ts to also detect locally-virtualized GPUs (QEMU/UTM), not just remote/forwarded displays.

Currently, hermes desktop silently fails to show a window on Ubuntu ARM64 running inside UTM (QEMU) on macOS. No crash, just a dbus/systemd cgroup warning (StartTransientUnit/UnitExists). The root cause is that detectRemoteDisplay() only checks for SSH sessions, X11 forwarding, and RDP, all remote-display cases. A UTM/QEMU guest's DISPLAY is local (:0), so none of those checks fire, GPU acceleration stays on, and Chromium's compositor never renders a usable window against the VM's virtualized/software-rendered GPU.

This mirrors the existing isWslEnvironment() pattern already in this file (a small, pure, testable detection function based on reading a platform-identifying file) rather than adding new CLI flags or env vars, since Hermes already has HERMES_DESKTOP_DISABLE_GPU and config.yaml's desktop.disable_gpu as manual overrides. This just fixes the automatic detection so users on this platform don't have to discover and set an override themselves.

Related Issue

Search performed per CONTRIBUTING.md "Before You Start": searched issues/PRs for UTM/QEMU + black screen/window-not-appearing on desktop. Related-but-not-duplicate results: #46523 ("Desktop window never appears on Wayland-in-VM (GNOME/Proxmox/VNC) ready-to-show never fires") is adjacent (also a VM display issue) but describes a different underlying cause (ready-to-show event, not GPU-compositor flicker/no-render on QEMU's virtual GPU) and a different VM stack (Proxmox/VNC vs UTM/QEMU). No exact duplicate found.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • apps/desktop/electron/bootstrap-platform.ts: added isVirtualizedGpuEnvironment(), which detects QEMU (and by extension UTM, which runs QEMU under the hood on macOS) via /sys/class/dmi/id/sys_vendor. Wired into detectRemoteDisplay() alongside the existing SSH/X11-forwarding/RDP checks, and exported alongside the other helpers.
  • Added isVirtualizedGpuEnvironment(), checked from detectRemoteDisplay(), to disable GPU acceleration on virtualized-GPU Linux guests. Detects two backends: QEMU (via DMI sys_vendor) and Apple's Virtualization.framework, UTM's default on Apple Silicon, via DMI product_name. Both signals are injectable for unit testing, mirroring the existing isWslEnvironment() pattern.

How to Test

  1. On a Linux ARM64 VM under UTM/QEMU (e.g. Ubuntu 26.04 guest on macOS host), with no HERMES_DESKTOP_DISABLE_GPU set and no desktop.disable_gpu in config.yaml, run hermes desktop.
  2. Before this change: the window fails to appear silently; only the dbus/systemd StartTransientUnit/UnitExists line is logged.
  3. After this change: the console should log [hermes] remote display detected (virtualized-gpu (QEMU/UTM detected via DMI sys_vendor)); disabling GPU hardware acceleration to prevent flicker, confirming GPU acceleration is disabled automatically. (See note below on this specific VM, GPU-disable alone wasn't sufficient to render a window; the sandbox/Ozone pieces are a separate, already-supported config mechanism, not part of this fix.)

Note: on this VM, GPU-acceleration disabling alone wasn't enough to get a
window to render --no-sandbox and --ozone-platform=x11 were also needed.
These aren't addressed by this PR since they're a separate mechanism from
the GPU auto-detection here (sandbox flag selection and Ozone backend
selection, not GPU compositor state) but they don't need a code fix either.
Hermes already supports this via config.yaml's desktop.electron_flags /
desktop.disable_gpu (see _desktop_launch_options() in
hermes_cli/main.py). Anyone hitting a similar "no window, no error" symptom
under nested virtualization can add:

desktop:
  disable_gpu: "1"
  electron_flags:
    - "--no-sandbox"
    - "--ozone-platform=x11"

One relevant detail for context: Hermes's automatic sandbox fixup
(_desktop_linux_sandbox_fixup) only falls back to --no-sandbox when it
fails to configure the SUID chrome-sandbox helper. On this VM the fixup
succeeded (root:root, 4755) but the sandbox still couldn't fully initialize
under QEMU/UTM's nested user-namespace restrictions silently, with no
fallback warning. So --no-sandbox has to be forced explicitly via config
here rather than relying on auto-detection.

UPDATED: Verified via console log that the virtualized-gpu reason (Apple Virtualization.framework path) fires correctly inside a real UTM/Apple Silicon guest. A separate, pre-existing race in the dev sandbox's backend startup (ECONNRESET/ECONNREFUSED connecting to the local API) currently blocks the window from rendering end-to-end in this environment, unrelated to this change, not something this PR attempts to fix.

Checklist

Code

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows or N/A
  • I've considered cross-platform impact (Windows, macOS) per the [compatibility guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#cross-platform-compatibility), the new isVirtualizedGpuEnvironment() check is Linux-only (platform !== 'linux' short-circuits), matching the existing pattern for the WSL and RDP checks in the same file, so it's a no-op on Windows/macOS.
  • I've updated tool descriptions/schemas if I changed tool behavior or N/A

Screenshots / Logs

Confirmed working on my own VM (Ubuntu 26.04 ARM64, UTM/QEMU on macOS): before this change, hermes desktop produced no window and only:

[4372:0714/141356.867968:ERROR:dbus/object_proxy.cc:573] Failed to call method: ...UnitExists...

I don't yet have a rebuilt binary reflecting this exact PR's code (edited via GitHub's web editor, not built locally), so I can't attach a fresh log showing the new virtualized-gpu detection line firing, but i'm happy to build and attach that if a maintainer wants it before merge.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) labels Jul 16, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating this to the existing pre-launch GPU policy. The premise is still present on current main: apps/desktop/electron/bootstrap-platform.ts:85-107 returns null for a local Linux display such as :0, while apps/desktop/electron/main.ts:178-184 disables hardware acceleration only for a non-null reason.

Problems

  • apps/desktop/electron/bootstrap-platform.ts:21 reads /sys/class/dmi/id/sys_vendor directly. Unlike isWslEnvironment(..., kernelRelease), this makes the QEMU path non-deterministic to test; the existing suite at apps/desktop/electron/bootstrap-platform.test.ts:12-85 has no coverage for the new branch.
  • The PR body states that disabling GPU alone did not render a window in the reported VM and that this exact commit was not rebuilt there. The user-visible result therefore is not yet verified.

Suggested changes

  • Make the DMI vendor value injectable and add deterministic QEMU/non-QEMU/read-failure/non-Linux tests plus override-precedence coverage.
  • Run the rebuilt commit in the reported guest and attach the detection result; scope the claim to GPU policy if the other required flags remain outside this change.

Automated hermes-sweeper review.

}
}

function isVirtualizedGpuEnvironment(platform = process.platform) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make the DMI vendor input injectable, analogous to isWslEnvironment(..., kernelRelease), and add deterministic tests for QEMU, non-QEMU, read failure, and non-Linux behavior. Reading the host sysfs file directly prevents the new branch from being tested through the existing detector suite.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the review feedback: both sysVendor and productName are now injectable (mirrors isWslEnvironment's kernelRelease pattern), and added 15 unit tests covering QEMU detection, sysfs-read failure, and the override behavior.
Also found and fixed a gap beyond the original scope: UTM's default backend on Apple Silicon is Apple's own Virtualization.framework, not QEMU, it reports the host vendor (Apple Inc.) via sys_vendor, so the original QEMU-only check would never have matched it. Added a second detection path via DMI product_name. This is likely the common case for UTM-on-Apple-Silicon users, so it's a meaningful expansion of what's actually covered, not just a testability refactor.
Verified in a real guest: the virtualized-gpu (Apple Virtualization.framework detected...) log line fires correctly. A separate pre-existing backend-startup race currently blocks full window rendering in this dev sandbox, unrelated to this fix, will file separately if needed.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
@nickmimic
nickmimic requested a review from teknium1 July 27, 2026 09:01
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Two PRs address distinct Linux desktop failure causes: #41236 configures and propagates a keychain backend so Electron safeStorage can persist tokens, while #65087 detects QEMU/UTM virtualized GPUs so the existing pre-launch policy disables hardware acceleration.

Related pull requests

  • feat(desktop): auto-detect Linux keychain backend for secure token storage #41236 related — (+397/-12) — verify n/a: The diff adds Linux keychain detection, a desktop.password_store configuration bridge, pre-ready --password-store application in main.ts, and focused Electron plus source/packaged launcher tests. Consistent with the automated keep_open verdict, the visible diff addresses its cited stale-entrypoint, configuration, and test concerns; preserve this implementation as a separate salvage path.
  • fix(desktop): detect QEMU/UTM virtualized GPUs, disable HW acceleration #65087 related — (+160/-9) — verify n/a: The diff adds injectable QEMU and Apple Virtualization.framework DMI detection with deterministic VM, non-VM, read-failure, non-Linux, and override-precedence tests. Consistent with the contributor keep_open review, preserve this isolated detection work, but its documented blocker remains: test the rebuilt commit in the reported guest and confirm that the window becomes usable.

Duplicates

None: #41236 addresses safeStorage keychain selection, whereas #65087 addresses Chromium rendering on virtualized GPUs.

Suggested consolidation

Keep both PRs open with separate salvage paths: retain #41236's keychain detection, config bridge, pre-ready Electron switch, and launcher tests; retain #65087's injectable virtualized-GPU detection and tests while requiring the contributor-requested end-to-end guest validation. Neither PR is a duplicate of the other.

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 33 kB of PR diffs, 10 kB of issue/PR text, 2 kB of discussion (2 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants