Skip to content

fix(desktop): resolve Exec to installed launcher, not raw checkout wrapper - #88709

Closed
nosliwhtes wants to merge 2 commits into
NousResearch:mainfrom
nosliwhtes:fix/desktop-exec-launcher
Closed

nosliwhtes wants to merge 2 commits into
NousResearch:mainfrom
nosliwhtes:fix/desktop-exec-launcher

Conversation

@nosliwhtes

Copy link
Copy Markdown

Summary

The Linux desktop entry generator (resolve_exec_command()) used resolve_hermes_bin() directly, which returns $HERMES_HOME/hermes when invoked via hermes desktop (argv[0] is the raw Python wrapper). That wrapper is #!/usr/bin/env python3 and fails outside a terminal with ModuleNotFoundError: dotenv because system Python has no venv deps.

Every hermes desktop launch rewrites ~/.local/share/applications/hermes.desktop, so the broken Exec was regenerated on every run — icon/menu clicks silently crashed while terminal launches kept working (and had to stay open because subprocess.run blocks).

Symptom

hermes.desktop[7458]: ModuleNotFoundError: No module named 'dotenv'
  File "/home/nosliwhtes/.hermes/hermes-agent/hermes", line 10
  • Clicking the Hermes icon does nothing.
  • hermes desktop from a terminal works but requires the terminal to stay open.
  • Fix is wiped on every hermes update / desktop rebuild because the generator re-creates the bad entry.

Fix

Add _is_raw_checkout_wrapper() (shebang probe) and prefer the installed bash wrapper ~/.local/bin/hermes (which execs the venv Python) for the Exec line. Falls back to python -m hermes_cli.main desktop only when no good launcher is on PATH.

Before:

Exec=/home/nosliwhtes/.hermes/hermes-agent/hermes desktop

After:

Exec=/home/nosliwhtes/.local/bin/hermes desktop

Verified: desktop-file-validate passes, resolve_exec_command() returns the installed launcher in both the argv[0]=hermes and argv[0]=~/.local/bin/hermes cases, and the regenerated entry survives manual edits.

Test plan

  • python -m hermes_cli.linux_desktop_entry shebang probe: raw wrapper → True, installed → False
  • resolve_exec_command() → /home/nosliwhtes/.local/bin/hermes desktop (both launch paths)
  • install_desktop_entry() writes correct Exec
  • desktop-file-validate passes
  • python -m py_compile passes

Fixes the "icon won't open, have to keep terminal open" issue plus the "breaks every update" regression.

…apper

The .desktop entry generator used resolve_hermes_bin() directly, which
returns $HERMES_HOME/hermes when run via `hermes desktop` (argv[0] is
the raw Python wrapper). That wrapper is `#!/usr/bin/env python3` and
fails outside a terminal with `ModuleNotFoundError: dotenv` because
system Python has no venv deps. Every `hermes desktop` launch rewrote
the entry with the broken path, so menu/icon clicks silently crashed
and required a terminal to stay open.

Add _is_raw_checkout_wrapper() and prefer the installed bash wrapper
~/.local/bin/hermes (which execs the venv Python) for the Exec line.
Falls back to `python -m hermes_cli.main desktop` only when no good
launcher is on PATH. Verified that the generated entry now reads
`Exec=/home/nosliwhtes/.local/bin/hermes desktop` and
desktop-file-validate passes.

Fixes: icon won't open the app without keeping terminal open; breaks
again on every update because the generator overwrote the entry.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) area/install-update Installer, updater, packaging, wheels, doctor needs-decision Awaiting maintainer decision before any implementation sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 17, 2026
@cYoren

cYoren commented Aug 18, 2026

Copy link
Copy Markdown

Curated verdict (needs-decision): mergeable as-is.

Verified against real code on the PR head 7b5d87515:

  • _is_raw_checkout_wrapper() byte-detects the #!/usr/bin/env python3 raw checkout wrapper (which breaks on app-menu launch with ModuleNotFoundError: dotenv), and resolve_exec_command() prefers the installed ~/.local/bin/hermes launcher on PATH.
  • Logic is sound: silently skips bad bins, prefers a non-raw PATH entry even when which disagrees with argv[0].
  • No new test added, but the PR does not regress the existing suite: tests/hermes_cli/test_linux_desktop_entry.py → 12 passed, 2 skipped.

Fixes silent crash on Linux app-menu launches (icon/menu clicks). Recommend approve/merge. Minor (non-blocking): a small regression test for the raw-wrapper detection would harden it.

@nosliwhtes

Copy link
Copy Markdown
Author

Thanks for verifying against the actual head instead of just reading the diff. That's the part I cared about most, since the failure mode here only shows up when the desktop file resolves through PATH.

Fair point on the missing test. I'll push a small regression case for _is_raw_checkout_wrapper() to this branch: one input that looks like a real installed launcher, one raw #!/usr/bin/env python3 wrapper, and something in between that should not match. Should be quick.

Seven cases for _is_raw_checkout_wrapper(): the raw python3 wrapper
itself, the installed bash launcher (must not match), a shebang below a
leading blank line (not at offset zero, must not match), the bare
shebang with no body, missing file, and directory path. Plus an end to
end check that resolve_exec_command() falls back to the interpreter
module instead of writing the broken wrapper into Exec=.

Also pin shutil.which to None in test_exec_falls_back_to_interpreter_module
so it no longer depends on the host having ~/.local/bin/hermes on PATH.
@nosliwhtes

Copy link
Copy Markdown
Author

Test is up: bd9c2ec, seven cases covering the raw wrapper, the installed bash launcher, offset-zero shebang detection, bare shebang, missing file, directory path, and the full fallback path end to end. Suite is 19 passed, 2 skipped locally.

One find while running this on real hardware: test_exec_falls_back_to_interpreter_module only stubs resolve_hermes_bin, not shutil.which. On any machine where ~/.local/bin/hermes is actually on PATH, the resolver correctly picks the launcher and that test fails. That's presumably why CI stayed green while the assumption never held off-sandbox. Pinned which() to None in the same commit.

@nosliwhtes

Copy link
Copy Markdown
Author

Closing this because #90492 merged the same root fix into main on August 21. The upstream implementation is broader: it detects any Python launcher whose shebang escapes the running environment, then prefixes a capable interpreter. Keeping _is_raw_checkout_wrapper() alongside it would duplicate the decision path rather than improve it.

Fresh verification against main 48f69e5: tests/hermes_cli/test_linux_desktop_entry.py is 15 passed, 2 skipped. The remaining uv/venv symlink edge case is being consolidated in #92122. I reproduced that on the original Zorin machine and posted the exact interpreter and Exec evidence there.

The tests added here did their job and helped pin the original failure, but they target a helper that no longer exists on main. Closing as implemented upstream, not abandoned.

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

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists 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.

3 participants