Conversation
resolve_exec_command() prefixed sys.executable after Path.resolve(), but uv-created venvs make bin/python a symlink into a shared base-interpreter tree. Resolving escapes the venv: CPython only discovers pyvenv.cfg from the executable path as invoked, so the generated hermes.desktop Exec ran the bare base interpreter and died silently (Terminal=false) on the first third-party import (ModuleNotFoundError: yaml) when launched from a launcher icon. Use sys.executable verbatim in both branches. Fixes NousResearch#94058
This is a precise fix for a real bug: The symlink test is well-constructed: it creates a real symlink chain, verifies that One minor observation: the test creates No concerns about the implementation — the |
What does this PR do?
Fixes the Linux launcher icon silently failing after every update on uv-based installs (the standard installer layout).
Root cause:
resolve_exec_command()prefixes the resolvedhermesscript withstr(Path(sys.executable).resolve()). uv-created venvs makevenv/bin/pythona symlink into the shared base-interpreter tree (~/.local/share/uv/python/cpython-3.11.16-.../bin/python3.11), and.resolve()follows that symlink. CPython discoverspyvenv.cfgfrom the executable path as invoked, so the generatedExec=line ran the bare base interpreter with no venv site-packages and died instantly on the first third-party import:— silently, because
Terminal=false.hermes desktopfrom a terminal worked (the bash wrapper execs the venv python), and each launch/rewrite of the entry re-broke it, which is why it recurred after every update.Fix: use
sys.executableverbatim (it is already absolute). The running process's imports came through exactly that path, so it is always at least as correct as the resolved path — resolving can only lose venv context, never gain anything.The existing code comment already states the intent ("sys.executable is the interpreter actually running Hermes (the venv one)");
.resolve()defeated it.Related Issue
Fixes #94058
(Also matches the symptoms in #92882, #92095, #92086, #91504, and duplicates #94564 / #94110. Related prior PR #94593 was closed by its author; this takes the simpler root-cause approach instead of the sibling-venv heuristic, and also fixes the
-m hermes_cli.mainfallback branch which had the same.resolve().)Type of Change
Changes Made
hermes_cli/linux_desktop_entry.py—resolve_exec_command(): usesys.executablewithout.resolve()in both the script-prefix branch and the-m hermes_cli.mainfallback branch; comment explains why resolving is wrong.tests/hermes_cli/test_linux_desktop_entry.py— updatedtest_exec_prefixes_interpreter_for_env_shebang_python_scriptto expect the unresolved interpreter; addedtest_exec_keeps_venv_symlink_interpreter_unresolved, a regression test that builds a uv-style layout (venvbin/python→ base-store symlink) and assertsExec=uses the unresolved venv path and never mentions the base interpreter.How to Test
~/.local/share/applications/hermes.desktop—Exec=must start with<repo>/venv/bin/python, not~/.local/share/uv/python/....<repo>/venv/bin/python -c "import yaml") — succeeds; the previously generated base interpreter fails.Verified on a live Omarchy/Arch install: before the fix the generated
Exec=failed withModuleNotFoundError: No module named 'yaml'; after the fix the regenerated entry imports cleanly anddesktop-file-validatepasses.Checklist
Code
fix(scope):,feat(scope):, etc.)scripts/run_tests.sh—tests/hermes_cli/test_linux_desktop_entry.py: 16/16 pass. Full suite: 37,409 passed, 202 failed; all failures reproduce identically on unmodifiedmainin this environment (15 in update-flow tests verified by re-running those files onmain; the rest areImportErrors in gateway/messaging tests because the throwaway test venv has only.[dev], not.[all,dev], extras). Zero failures overlap with this change.Documentation & Housekeeping
docs/, docstrings) — N/A (behavior-only fix; updated the code comment that documented the old approach)cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Ais_supported();sys.executableis absolute on all platformsScreenshots / Logs
Generated entry before (broken):
Generated entry after (works):