# NousResearch/hermes-agent · Hardcoded venv path bugs and fixes across CLI/gateway/docs

## Open now

- 4 issues, 6 PRs open on GitHub.
- #35293: doctor: Homebrew install falsely warns 'Venv entry point not found'
- #43250: Bug: _venv_scripts_dir() only checks "venv", ignores ".venv"
- #49529: \[Bug\]: PyPI 0.17.0 wheel install: doctor false-positive venv entry point warning and optional-skills missing from wheel
- #50581: bug: doctor/update guidance assumes PROJECT_ROOT/venv and misses active sidecar venv
- PR #35300: fix(doctor): recognize Homebrew/uv-tool installs in venv entry point check
- PR #35303: fix(doctor): restrict managed-install detection to docker and nixos
- PR #43303: fix(doctor): align command-link check with install.sh layouts (Termux, root/FHS, isolated HOME)
- PR #50592: fix(cli): respect active sidecar venv paths
- PR #50704: Fix update paths for project .venv installs
- PR #61076: fix(cli): detect hermes root in package installations (Brew, pip)

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.

## TL;DR

- 39 member(s): 4 open issue(s), 5 closed issue(s), 6 open PR(s), 3 merged PR(s), 21 PR(s) closed without merging.
- A fix was merged to main in PR #2114. Verify it addresses your case.
- **Recommended:** read PR #2114 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-2114,pr-2797,pr-123011`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.05`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #35293: doctor: Homebrew install falsely warns 'Venv entry point not found'
- #43250: Bug: _venv_scripts_dir() only checks "venv", ignores ".venv"
- #49529: \[Bug\]: PyPI 0.17.0 wheel install: doctor false-positive venv entry point warning and optional-skills missing from wheel
- #50581: bug: doctor/update guidance assumes PROJECT_ROOT/venv and misses active sidecar venv

## Closed / duplicate issues

- #2066: \[Bug\]: wrong venv path?
- #2492: bug(gateway): systemd service unit hardcodes venv/ path, breaks when virtualenv is .venv
- #39714: Bug: hermes update installs deps into hardcoded venv/ while active env is .venv (duplicate orphan virtualenv)
- #49665: _venv_scripts_dir() hardcodes "venv" — silently disables quarantine and concurrent-instance detection when venv is at .venv
- #61056: \[Bug\]: Installing hermes via Brew break the hermes desktop installation.

## Prior & competing PRs

- **#2114: docs: align venv path to match installer (venv/ not .venv/)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `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`
- **#2797: fix(gateway): detect virtualenv path instead of hardcoding venv/ (#2492)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/gateway.py`, `tests/hermes_cli/test_gateway_service.py`
  - fix hunks:
  - `hermes_cli/gateway.py`

    ```diff
    @@ -371,13 +371,37 @@ def print_systemd_linger_guidance() -> None:
     def get_launchd_plist_path() -> Path:
         return Path.home() / "Library" / "LaunchAgents" / "ai.hermes.gateway.plist"
     
    +def _detect_venv_dir() -> Path | None:
    +    """Detect the active virtualenv directory.
    +
    +    Checks ``sys.prefix`` first (works regardless of the directory name),
    +    then falls back to probing common directory names under PROJECT_ROOT.
    +    Returns ``None`` when no virtualenv can be found.
    +    """
    +    # If we're running inside a virtualenv, sys.prefix points to it.
    +    if sys.prefix != sys.base_prefix:
    +        venv = Path(sys.prefix)
    +        if venv.is_dir():
    +            return venv
    +
    +    # Fallback: check common virtualenv directory names under the project root.
    +    for candidate in (".venv", "venv"):
    +        venv = PROJECT_ROOT / candidate
    +        if venv.is_dir():
    +            return venv
    +
    +    return None
    +
    +
     def get_python_path() -> str:
    -    if is_windows():
    -        venv_python = PROJECT_ROOT / "venv" / "Scripts" / "python.exe"
    -    else:
    -        venv_python = PROJECT_ROOT / "venv" / "bin" / "python"
    -    if venv_python.exists():
    -        return str(venv_python)
    +    venv = _detect_venv_dir()
    +    if venv is not None:
    +        if is_windows():
    +            venv_python = venv / "Scripts" / "python.exe"
    +        else:
    +            venv_python = venv / "bin" / "python"
    +        if venv_python.exists():
    +            return str(venv_python)
         return sys.executable
     
     def get_hermes_cli_path() -> str:
    ```

    ```diff
    @@ -399,8 +423,9 @@ def get_hermes_cli_path() -> str:
     def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) -> str:
         python_path = get_python_path()
         working_dir = str(PROJECT_ROOT)
    -    venv_dir = str(PROJECT_ROOT / "venv")
    -    venv_bin = str(PROJECT_ROOT / "venv" / "bin")
    +    detected_venv = _detect_venv_dir()
    +    venv_dir = str(detected_venv) if detected_venv else str(PROJECT_ROOT / "venv")
    +    venv_bin = str(detected_venv / "bin") if detected_venv else str(PROJECT_ROOT / "venv" / "bin")
         node_bin = str(PROJECT_ROOT / "node_modules" / ".bin")
     
         path_entries = [venv_bin, node_bin]
    ```
  - `tests/hermes_cli/test_gateway_service.py`

    ```diff
    @@ -282,6 +282,78 @@ def test_gateway_restart_does_not_fallback_to_foreground_when_launchd_restart_fa
             assert run_calls == []
     
     
    +class TestDetectVenvDir:
    +    """Tests for _detect_venv_dir() virtualenv detection."""
    +
    +    def test_detects_active_virtualenv_via_sys_prefix(self, tmp_path, monkeypatch):
    +        venv_path = tmp_path / "my-custom-venv"
    +        venv_path.mkdir()
    +        monkeypatch.setattr("sys.prefix", str(venv_path))
    +        monkeypatch.setattr("sys.base_prefix", "/usr")
    +
    +        result = gateway_cli._detect_venv_dir()
    +        assert result == venv_path
    +
    +    def test_falls_back_to_dot_venv_directory(self, tmp_path, monkeypatch):
    +        # Not inside a virtualenv
    +        monkeypatch.setattr("sys.prefix", "/usr")
    +        monkeypatch.setattr("sys.base_prefix", "/usr")
    +        monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
    +
    +        dot_venv = tmp_path / ".venv"
    +        dot_venv.mkdir()
    +
    +        result = gateway_cli._detect_venv_dir()
    +        assert result == dot_venv
    +
    +    def test_falls_back_to_venv_directory(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr("sys.prefix", "/usr")
    +        monkeypatch.setattr("sys.base_prefix", "/usr")
    +        monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
    +
    +        venv = tmp_path / "venv"
    +        venv.mkdir()
    +
    +        result = gateway_cli._detect_venv_dir()
    +        assert result == venv
    +
    +    def test_prefers_dot_venv_over_venv(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr("sys.prefix", "/usr")
    +        monkeypatch.setattr("sys.base_prefix", "/usr")
    +        monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
    +
    +        (tmp_path / ".venv").mkdir()
    +        (tmp_path / "venv").mkdir()
    +
    +        result = gateway_cli._detect_venv_dir()
    +        assert result == tmp_path / ".venv"
    +
    +    def test_returns_none_when_no_virtualenv(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr("sys.prefix", "/usr")
    +        monkeypatch.setattr("sys.base_prefix", "/usr")
    +        monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
    +
    +        result = gateway_cli._detect_venv_dir()
    +        assert result is None
    +
    +
    +class TestGeneratedUnitUsesDetectedVenv:
    +    def test_systemd_unit_uses_dot_venv_when_detected(self, tmp_path, monkeypatch):
    +        dot_venv = tmp_path / ".venv"
    +        dot_venv.mkdir()
    +        (dot_venv / "bin").mkdir()
    +
    +        monkeypatch.setattr(gateway_cli, "_detect_venv_dir", lambda: dot_venv)
    +        monkeypatch.setattr(gateway_cli, "get_python_path", lambda: str(dot_venv / "bin" / "python"))
    +
    +        unit = gateway_cli.generate_systemd_unit(system=False)
    +
    +        assert f"VIRTUAL_ENV={dot_venv}" in unit
    +        assert f"{dot_venv}/bin" in unit
    +        # Must NOT contain a hardcoded /venv/ path
    +        assert "/venv/" not in unit or "/.venv/" in unit
    +
    +
     class TestEnsureUserSystemdEnv:
         """Tests for _ensure_user_systemd_env() D-Bus session bus auto-detection."""
     
    ```
- **#40191: fix(cli): repair hermes update path (venv detection, uv arity, v25→v26 migration)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`
- **#43374: fix(cli): check both venv and .venv directories for script shims (#43250)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#43258`, `#43250`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#49566: fix(cli): resolve false-positive venv entry point warning in doctor (#49529)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#123011: fix(desktop): handle package-manager installs with no desktop source tree**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/main_desktop.py`, `tests/hermes_cli/test_gui_command.py`
- **#2087: fix(docs): handle both venv and .venv in AGENTS.md activation command**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`
- **#2493: fix(gateway): detect virtualenv path instead of hardcoding venv/**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#2797`
- **#2500: fix(gateway): detect .venv and venv directories for systemd unit generation**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#2493`, `#2797`
- **#2518: fix(gateway): dynamically detect virtualenv path in systemd unit generation (#2492)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#2493`, `#2797`
- **#35300: fix(doctor): recognize Homebrew/uv-tool installs in venv entry point check**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#35293`
  - files: `hermes_cli/doctor.py`, `tests/hermes_cli/test_doctor_command_install.py`
- **#35303: fix(doctor): restrict managed-install detection to docker and nixos**. open
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`
  - files: `hermes_cli/doctor.py`, `tests/hermes_cli/test_doctor.py`
- **#39735: fix(cli): resolve active venv directory dynamically instead of hardcoding "venv"**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#39749: fix(update): resolve venv from active interpreter instead of hardcoded path**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#39752: fix(cli): target active venv during source update**. closed without merge
  - maintainer direction: category `silent_close`; implication `active`
- **#40207: fix(cli): resolve the real venv root in hermes update instead of hardcoded venv/**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#40994: Fix #39714: Replace hardcoded venv paths with detection**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#43258: fix(cli): check both venv and .venv directories in _venv_scripts_dir()**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#43262: fix(cli): make _venv_scripts_dir() check .venv before venv**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#43303: fix(doctor): align command-link check with install.sh layouts (Termux, root/FHS, isolated HOME)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/doctor.py`, `tests/hermes_cli/test_doctor_command_install.py`
- **#43707: fix: detect .venv in _venv_scripts_dir() and block cronjob toolset in child agents **. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#49666: fix(windows): detect .venv and use sys.prefix for venv path resolution**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#49842: fix(update): resolve venv dir for both \`venv\` and \`.venv\` layouts (#49665)**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`
- **#50592: fix(cli): respect active sidecar venv paths**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `b14d75f8a`
  - files: `hermes_cli/doctor.py`, `hermes_cli/main.py`, `tests/hermes_cli/test_doctor_command_install.py`, `tests/hermes_cli/test_update_interrupted_recovery.py`
- **#50704: Fix update paths for project .venv installs**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/main.py`, `tests/hermes_cli/test_cmd_update.py`, `tests/hermes_cli/test_update_concurrent_quarantine.py`
- **#61076: fix(cli): detect hermes root in package installations (Brew, pip)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/main.py`, `tests/hermes_cli/test_find_hermes_root.py`
- **#61085: fix: resolve desktop GUI source via HERMES_DESKTOP_SOURCE env var for managed installs (#61056)**. closed without merge
  - maintainer direction: category `stale_abandoned`; implication `active`
- **#62191: fix(update): resolve venv dir for both venv/ and .venv/ layouts**. closed without merge
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`; refs `3700dd5151d98df8dfddcf497c08ad8e735d02fd`
- **#77428: fix(doctor): detect environment-installed entry points (no false 'Venv entry point not found')**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `e6c862d9f`
- **#99478: fix(desktop): give actionable error when Electron source tree absent from wheel/Brew installs**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#99443`, `#99448`

## Fix status

- Merged to main in PR #2114 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-09-25`.
- Subsystem: `docs`
