{"repo":"NousResearch/hermes-agent","public_id":"2066","display_label":"Hardcoded venv path bugs and fixes across CLI/gateway/docs","summary":"Multiple bugs and corresponding fixes address hardcoded 'venv' directory assumptions throughout the CLI, gateway, doctor, and docs, which break when the active virtualenv is named '.venv' or located elsewhere. Several related issues cover Brew installation conflicts, systemd unit generation, and doctor false-positives stemming from the same path detection gap.","subsystem":"docs","counts":{"members":39,"open_issues":4,"open_issues_distinct":4,"open_prs":6,"closed_issues":5,"merged_prs":3,"attempt_prs":27},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":2114},"resolution":{"state":"partially_resolved","by":"pr-2114,pr-2797,pr-123011"},"freshness":{"state":"verified","last_verified_at":"2026-09-25T20:57:59+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.0470715249662618},"fix_edges":[{"issue_number":2066,"pr_number":2114,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":2492,"pr_number":2797,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":61056,"pr_number":123011,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":2066,"title":"[Bug]: wrong venv path?","url":null,"state":"closed"},{"kind":"issue","number":2492,"title":"bug(gateway): systemd service unit hardcodes venv/ path, breaks when virtualenv is .venv","url":null,"state":"closed"},{"kind":"issue","number":35293,"title":"doctor: Homebrew install falsely warns 'Venv entry point not found'","url":null,"state":"open"},{"kind":"issue","number":39714,"title":"Bug: hermes update installs deps into hardcoded venv/ while active env is .venv (duplicate orphan virtualenv)","url":null,"state":"closed"},{"kind":"issue","number":43250,"title":"Bug: _venv_scripts_dir() only checks \"venv\", ignores \".venv\"","url":null,"state":"open"},{"kind":"issue","number":49529,"title":"[Bug]: PyPI 0.17.0 wheel install: doctor false-positive venv entry point warning and optional-skills missing from wheel","url":null,"state":"open"},{"kind":"issue","number":49665,"title":"_venv_scripts_dir() hardcodes \"venv\" — silently disables quarantine and concurrent-instance detection when venv is at .venv","url":null,"state":"closed"},{"kind":"issue","number":50581,"title":"bug: doctor/update guidance assumes PROJECT_ROOT/venv and misses active sidecar venv","url":null,"state":"open"},{"kind":"issue","number":61056,"title":"[Bug]: Installing hermes via Brew break the hermes desktop installation.","url":null,"state":"closed"},{"kind":"pr","number":2087,"title":"fix(docs): handle both venv and .venv in AGENTS.md activation command","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":2114,"title":"docs: align venv path to match installer (venv/ not .venv/)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["AGENTS.md","README.md","skills/dogfood/hermes-agent-setup/SKILL.md","skills/mlops/training/hermes-atropos-environments/references/usage-patterns.md","website/docs/developer-guide/adding-providers.md"]},{"kind":"pr","number":2493,"title":"fix(gateway): detect virtualenv path instead of hardcoding venv/","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#2797","number":2797}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2500,"title":"fix(gateway): detect .venv and venv directories for systemd unit generation","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#2493","number":2493},{"kind":"pr","value":"#2797","number":2797}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2518,"title":"fix(gateway): dynamically detect virtualenv path in systemd unit generation (#2492)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#2493","number":2493},{"kind":"pr","value":"#2797","number":2797}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2797,"title":"fix(gateway): detect virtualenv path instead of hardcoding venv/ (#2492)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/gateway.py","tests/hermes_cli/test_gateway_service.py"],"fix_hunks":[{"file":"hermes_cli/gateway.py","hunks":["@@ -371,13 +371,37 @@ def print_systemd_linger_guidance() -> None:\n def get_launchd_plist_path() -> Path:\n     return Path.home() / \"Library\" / \"LaunchAgents\" / \"ai.hermes.gateway.plist\"\n \n+def _detect_venv_dir() -> Path | None:\n+    \"\"\"Detect the active virtualenv directory.\n+\n+    Checks ``sys.prefix`` first (works regardless of the directory name),\n+    then falls back to probing common directory names under PROJECT_ROOT.\n+    Returns ``None`` when no virtualenv can be found.\n+    \"\"\"\n+    # If we're running inside a virtualenv, sys.prefix points to it.\n+    if sys.prefix != sys.base_prefix:\n+        venv = Path(sys.prefix)\n+        if venv.is_dir():\n+            return venv\n+\n+    # Fallback: check common virtualenv directory names under the project root.\n+    for candidate in (\".venv\", \"venv\"):\n+        venv = PROJECT_ROOT / candidate\n+        if venv.is_dir():\n+            return venv\n+\n+    return None\n+\n+\n def get_python_path() -> str:\n-    if is_windows():\n-        venv_python = PROJECT_ROOT / \"venv\" / \"Scripts\" / \"python.exe\"\n-    else:\n-        venv_python = PROJECT_ROOT / \"venv\" / \"bin\" / \"python\"\n-    if venv_python.exists():\n-        return str(venv_python)\n+    venv = _detect_venv_dir()\n+    if venv is not None:\n+        if is_windows():\n+            venv_python = venv / \"Scripts\" / \"python.exe\"\n+        else:\n+            venv_python = venv / \"bin\" / \"python\"\n+        if venv_python.exists():\n+            return str(venv_python)\n     return sys.executable\n \n def get_hermes_cli_path() -> str:","@@ -399,8 +423,9 @@ def get_hermes_cli_path() -> str:\n def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) -> str:\n     python_path = get_python_path()\n     working_dir = str(PROJECT_ROOT)\n-    venv_dir = str(PROJECT_ROOT / \"venv\")\n-    venv_bin = str(PROJECT_ROOT / \"venv\" / \"bin\")\n+    detected_venv = _detect_venv_dir()\n+    venv_dir = str(detected_venv) if detected_venv else str(PROJECT_ROOT / \"venv\")\n+    venv_bin = str(detected_venv / \"bin\") if detected_venv else str(PROJECT_ROOT / \"venv\" / \"bin\")\n     node_bin = str(PROJECT_ROOT / \"node_modules\" / \".bin\")\n \n     path_entries = [venv_bin, node_bin]"]},{"file":"tests/hermes_cli/test_gateway_service.py","hunks":["@@ -282,6 +282,78 @@ def test_gateway_restart_does_not_fallback_to_foreground_when_launchd_restart_fa\n         assert run_calls == []\n \n \n+class TestDetectVenvDir:\n+    \"\"\"Tests for _detect_venv_dir() virtualenv detection.\"\"\"\n+\n+    def test_detects_active_virtualenv_via_sys_prefix(self, tmp_path, monkeypatch):\n+        venv_path = tmp_path / \"my-custom-venv\"\n+        venv_path.mkdir()\n+        monkeypatch.setattr(\"sys.prefix\", str(venv_path))\n+        monkeypatch.setattr(\"sys.base_prefix\", \"/usr\")\n+\n+        result = gateway_cli._detect_venv_dir()\n+        assert result == venv_path\n+\n+    def test_falls_back_to_dot_venv_directory(self, tmp_path, monkeypatch):\n+        # Not inside a virtualenv\n+        monkeypatch.setattr(\"sys.prefix\", \"/usr\")\n+        monkeypatch.setattr(\"sys.base_prefix\", \"/usr\")\n+        monkeypatch.setattr(gateway_cli, \"PROJECT_ROOT\", tmp_path)\n+\n+        dot_venv = tmp_path / \".venv\"\n+        dot_venv.mkdir()\n+\n+        result = gateway_cli._detect_venv_dir()\n+        assert result == dot_venv\n+\n+    def test_falls_back_to_venv_directory(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(\"sys.prefix\", \"/usr\")\n+        monkeypatch.setattr(\"sys.base_prefix\", \"/usr\")\n+        monkeypatch.setattr(gateway_cli, \"PROJECT_ROOT\", tmp_path)\n+\n+        venv = tmp_path / \"venv\"\n+        venv.mkdir()\n+\n+        result = gateway_cli._detect_venv_dir()\n+        assert result == venv\n+\n+    def test_prefers_dot_venv_over_venv(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(\"sys.prefix\", \"/usr\")\n+        monkeypatch.setattr(\"sys.base_prefix\", \"/usr\")\n+        monkeypatch.setattr(gateway_cli, \"PROJECT_ROOT\", tmp_path)\n+\n+        (tmp_path / \".venv\").mkdir()\n+        (tmp_path / \"venv\").mkdir()\n+\n+        result = gateway_cli._detect_venv_dir()\n+        assert result == tmp_path / \".venv\"\n+\n+    def test_returns_none_when_no_virtualenv(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(\"sys.prefix\", \"/usr\")\n+        monkeypatch.setattr(\"sys.base_prefix\", \"/usr\")\n+        monkeypatch.setattr(gateway_cli, \"PROJECT_ROOT\", tmp_path)\n+\n+        result = gateway_cli._detect_venv_dir()\n+        assert result is None\n+\n+\n+class TestGeneratedUnitUsesDetectedVenv:\n+    def test_systemd_unit_uses_dot_venv_when_detected(self, tmp_path, monkeypatch):\n+        dot_venv = tmp_path / \".venv\"\n+        dot_venv.mkdir()\n+        (dot_venv / \"bin\").mkdir()\n+\n+        monkeypatch.setattr(gateway_cli, \"_detect_venv_dir\", lambda: dot_venv)\n+        monkeypatch.setattr(gateway_cli, \"get_python_path\", lambda: str(dot_venv / \"bin\" / \"python\"))\n+\n+        unit = gateway_cli.generate_systemd_unit(system=False)\n+\n+        assert f\"VIRTUAL_ENV={dot_venv}\" in unit\n+        assert f\"{dot_venv}/bin\" in unit\n+        # Must NOT contain a hardcoded /venv/ path\n+        assert \"/venv/\" not in unit or \"/.venv/\" in unit\n+\n+\n class TestEnsureUserSystemdEnv:\n     \"\"\"Tests for _ensure_user_systemd_env() D-Bus session bus auto-detection.\"\"\"\n \n"]}]},{"kind":"pr","number":35300,"title":"fix(doctor): recognize Homebrew/uv-tool installs in venv entry point check","url":null,"state":"open","close_reason":{"refs":[{"kind":"pr","value":"#35293","number":35293}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":35303,"title":"fix(doctor): restrict managed-install detection to docker and nixos","url":null,"state":"open","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":39735,"title":"fix(cli): resolve active venv directory dynamically instead of hardcoding \"venv\"","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":39749,"title":"fix(update): resolve venv from active interpreter instead of hardcoded path","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":39752,"title":"fix(cli): target active venv during source update","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":40191,"title":"fix(cli): repair hermes update path (venv detection, uv arity, v25→v26 migration)","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":40207,"title":"fix(cli): resolve the real venv root in hermes update instead of hardcoded venv/","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":40994,"title":"Fix #39714: Replace hardcoded venv paths with detection","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":43258,"title":"fix(cli): check both venv and .venv directories in _venv_scripts_dir()","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":43262,"title":"fix(cli): make _venv_scripts_dir() check .venv before venv","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":43303,"title":"fix(doctor): align command-link check with install.sh layouts (Termux, root/FHS, isolated HOME)","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":43374,"title":"fix(cli): check both venv and .venv directories for script shims (#43250)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#43258","number":43258},{"kind":"pr","value":"#43250","number":43250}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":43707,"title":"fix: detect .venv in _venv_scripts_dir() and block cronjob toolset in child agents ","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":49566,"title":"fix(cli): resolve false-positive venv entry point warning in doctor (#49529)","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":49666,"title":"fix(windows): detect .venv and use sys.prefix for venv path resolution","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":49842,"title":"fix(update): resolve venv dir for both `venv` and `.venv` layouts (#49665)","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":50592,"title":"fix(cli): respect active sidecar venv paths","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"b14d75f8a","number":0}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":50704,"title":"Fix update paths for project .venv installs","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":61076,"title":"fix(cli): detect hermes root in package installations (Brew, pip)","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":61085,"title":"fix: resolve desktop GUI source via HERMES_DESKTOP_SOURCE env var for managed installs (#61056)","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":62191,"title":"fix(update): resolve venv dir for both venv/ and .venv/ layouts","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"3700dd5151d98df8dfddcf497c08ad8e735d02fd","number":0}],"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":77428,"title":"fix(doctor): detect environment-installed entry points (no false 'Venv entry point not found')","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"e6c862d9f","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":99478,"title":"fix(desktop): give actionable error when Electron source tree absent from wheel/Brew installs","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#99443","number":99443},{"kind":"pr","value":"#99448","number":99448}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":123011,"title":"fix(desktop): handle package-manager installs with no desktop source tree","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/main_desktop.py","tests/hermes_cli/test_gui_command.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}