Skip to content

fix(desktop): keep venv interpreter unresolved in .desktop Exec - #96396

Closed
cosminfuica wants to merge 1 commit into
NousResearch:mainfrom
cosminfuica:fix/desktop-entry-venv-symlink
Closed

cosminfuica wants to merge 1 commit into
NousResearch:mainfrom
cosminfuica:fix/desktop-entry-venv-symlink

Conversation

@cosminfuica

Copy link
Copy Markdown

Summary

On Linux installs where the Hermes venv was created by uv (the shell installer's default), hermes desktop writes a non-runnable Exec= into ~/.local/share/applications/hermes.desktop. Clicking the launcher icon silently does nothing.

Reported in #94058, #94110, #92086, #92095 (and #90292 is the same class). This fixes both halves of the bug and adds regression coverage.

Root cause

resolve_exec_command() writes Path(sys.executable).resolve().

uv venv (like python -m venv --symlinks, the POSIX default) makes venv/bin/python a symlink into a shared interpreter store:

venv/bin/python -> ~/.local/share/uv/python/cpython-3.11.16-linux-x86_64-gnu/bin/python3.11

.resolve() follows that symlink, so Exec= names the base interpreter. Its sys.prefix is the uv store, not the venv, so site-packages is never on sys.path and Hermes dies on its first third-party import:

ModuleNotFoundError: No module named 'yaml'

Because the entry sets Terminal=false, that traceback goes nowhere — the icon just appears inert. Reproduced on this machine:

$ env -i HOME=$HOME /home/…/uv/python/cpython-3.11.16-…/bin/python3.11 /home/…/hermes-agent/hermes desktop
ModuleNotFoundError: No module named 'yaml'     # exit 1

$ env -i HOME=$HOME /home/…/hermes-agent/venv/bin/python /home/…/hermes-agent/hermes desktop
# exit 0

Two consequences, one cause:

  1. Broken imports — the resolved interpreter cannot see the venv.
  2. Version pinning — the resolved path embeds cpython-3.11.16. When uv upgrades to 3.11.17 the path disappears, so an install that was working breaks at the next update. This is why the reports describe the icon breaking repeatedly after upgrades.

_needs_interpreter() had the mirror-image mistake: it compared the shebang against the resolved parent dir, so a venv console script whose shebang legitimately reads #!/…/venv/bin/python3 never matched and got needlessly prefixed.

Fix

Use os.path.abspath(sys.executable) — already absolute, so .resolve() bought nothing — and keep the symlink intact. Match the shebang against both the unresolved and resolved bin dirs.

Keeping the symlink also preserves PEP 405 venv detection, which locates pyvenv.cfg relative to the unresolved executable path.

Before / after on a uv install:

-Exec=/home/u/.local/share/uv/python/cpython-3.11.16-linux-x86_64-gnu/bin/python3.11 /home/u/.hermes/hermes-agent/venv/bin/hermes desktop
+Exec=/home/u/.hermes/hermes-agent/venv/bin/hermes desktop

Testing

  • pytest tests/hermes_cli/test_linux_desktop_entry.py → 17 passed, 2 skipped
  • Two new regression tests build a real symlinked uv-style venv in tmp_path and assert the version-pinned base interpreter never reaches Exec=. Both fail on main and pass with this patch (verified by reverting the one-line change).
  • One existing assertion was updated: it froze Path(sys.executable).resolve(), i.e. the buggy value itself. Its stated intent (Linux desktop entry generated with non-runnable Exec; icon launch always fails #90292 — "prefix the running interpreter") is preserved; it now asserts the interpreter is absolute and venv-internal rather than snapshotting the resolved path. Per AGENTS.md, behavior contracts over snapshots.
  • End-to-end: regenerated the entry, desktop-file-validate passes, and launching the emitted Exec under env -i (no PATH, no venv activation — what the DE actually does) starts the app cleanly.

Notes

Affects every Linux user whose venv python is a symlink — the default for both uv venv and python -m venv on POSIX. No behavior change on Windows, on non-symlinked venvs, or for the bash-wrapper and native-shim launcher paths, which are covered by existing tests.

resolve_exec_command() wrote Path(sys.executable).resolve() into Exec=.
On a uv-created venv, venv/bin/python is a symlink into the shared
interpreter store (~/.local/share/uv/python/cpython-X.Y.Z-.../bin/),
so .resolve() emitted the BASE interpreter. That interpreter's
sys.prefix is the uv install, not the venv, so none of Hermes'
dependencies import: the app died on 'import yaml' before drawing a
window. With Terminal=false the traceback is discarded and the launcher
icon appears inert. The resolved path also pins a patch version that
vanishes when uv upgrades 3.11.16 -> 3.11.17.

_needs_interpreter() made the same mistake in reverse: it compared the
shebang against the RESOLVED parent dir, so a venv console script
(#!/.../venv/bin/python3) never matched and was needlessly prefixed.

Use os.path.abspath(sys.executable) (already absolute; keeps the symlink)
and match the shebang against both the unresolved and resolved bin dirs.
Keeping the symlink also preserves PEP 405 venv detection.

Adds regression coverage for the uv symlink layout.
@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 sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades duplicate This issue or pull request already exists labels Aug 27, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Duplicate of #92090. Both preserve the unresolved venv interpreter in Linux desktop Exec= and repair venv-shebang recognition; #92090 is the earlier canonical repair.

@cosminfuica

Copy link
Copy Markdown
Author

Closing in favor of #92090, the earlier canonical fix for this bug class (same repair: unresolved interpreter in Exec= + _needs_interpreter matching both spellings). Triage was right to flag the duplicate — #92090 predates this by five days; I missed it when searching open PRs.

What #92090 was missing was a run on an affected Linux host (its author is on Windows) — I've now done that end-to-end verification on my uv-venv machine and posted the results there: tests green, mutation proof against the merge-base, before/after Exec= emission in four launcher scenarios, and env -i launches of the emitted lines. See #92090 (comment).

For the consolidating maintainer: #96777 has a comparison table of all six open PRs for this defect.

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/*) duplicate This issue or pull request already exists 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.

2 participants