# NousResearch/hermes-agent · Python virtual environment variables leaking into subprocesses

## Open now

- Nothing open on GitHub.

Multiple issues and fixes address VIRTUAL_ENV, CONDA_PREFIX, and PYTHONPATH environment variables leaking from the application into child processes, contaminating unrelated Python environments and tools. A few unrelated fixes (Windows signing, cron fallback) are also present in the group.

## TL;DR

- 14 member(s): 0 open issue(s), 3 closed issue(s), 0 open PR(s), 2 merged PR(s), 9 PR(s) closed without merging.
- A fix was merged to main in PR #53792. Verify it addresses your case.
- **Recommended:** read PR #53792 (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-53792,pr-133868`. 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.13`: 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.

## Closed / duplicate issues

- #23473: Gateway leaks VIRTUAL_ENV into subprocesses; agent's \`uv sync\` in any project bricks Hermes' own venv
- #57467: \[Bug\]: _ensure_windows_gateway_venv_imports leaks PYTHONPATH into global environment, poisoning non-Hermes Python processes
- #65909: PYTHONPATH leaks to child processes, breaking pipx-installed CLI tools with C extension dependencies

## Prior & competing PRs

- **#23474: fix(terminal): strip VIRTUAL_ENV from agent subprocess envs**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#29980`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#53792: fix(terminal): strip VIRTUAL_ENV/CONDA_PREFIX from terminal subprocess env**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `scripts/release.py`, `tests/tools/test_local_env_blocklist.py`, `tools/environments/local.py`
  - fix hunks:
  - `scripts/release.py`

    ```diff
    @@ -45,6 +45,7 @@
     
     # Auto-extracted from noreply emails + manual overrides
     AUTHOR_MAP = {
    +    "[redacted:email]": "dalenguyen",  # PR #53678 salvage (strip VIRTUAL_ENV/CONDA_PREFIX from terminal subprocess env; #23473)
         "[redacted:email]": "Blaryxoff",  # PR #32602 salvage (deep-merge PUT /api/config to preserve unrelated sections; #13396)
         "[redacted:email]": "DiamondEyesFox",  # PR #53351 salvage (rebaseline in-place compression flushes to prevent duplicate compacted rows; #9096)
         "[redacted:email]": "rlaope",  # PR #35075 salvage (align cron invisible-unicode set with install-time scanner; #35075)
    ```
  - `tests/tools/test_local_env_blocklist.py`

    ```diff
    @@ -239,6 +239,54 @@ def test_force_prefix_overrides_os_environ_block(self):
             assert result_env["OPENAI_BASE_URL"] == "http://intended/v1"
     
     
    +class TestActiveVenvMarkerStripping:
    +    """Active-virtualenv markers must not leak into terminal subprocesses (#23473).
    +
    +    The gateway runs inside its own venv, so its process environment carries
    +    VIRTUAL_ENV (and possibly CONDA_PREFIX). If those leak into commands the
    +    agent runs against ANOTHER Python project, ``uv``/``poetry`` treat the
    +    inherited value as the active environment and build that project's deps
    +    into the Hermes venv path instead of the project's own ``.venv`` —
    +    silently clobbering the Hermes environment (and, when the other project
    +    pins a different Python, breaking the gateway outright). The Hermes venv
    +    stays reachable via PATH, so stripping the markers is safe.
    +    """
    +
    +    def test_virtualenv_marker_stripped_end_to_end(self):
    +        result_env = _run_with_env(extra_os_env={
    +            "VIRTUAL_ENV": "/home/user/.hermes/hermes-agent/venv",
    +        })
    +        assert "VIRTUAL_ENV" not in result_env
    +
    +    def test_conda_prefix_marker_stripped_end_to_end(self):
    +        result_env = _run_with_env(extra_os_env={
    +            "CONDA_PREFIX": "/opt/conda/envs/hermes",
    +        })
    +        assert "CONDA_PREFIX" not in result_env
    +
    +    def test_make_run_env_strips_markers(self):
    +        from tools.environments.local import _make_run_env
    +        poison = {"VIRTUAL_ENV": "/venv", "CONDA_PREFIX": "/conda", "PATH": "/usr/bin"}
    +        with patch.dict(os.environ, poison, clear=True):
    +            result = _make_run_env({})
    +        assert "VIRTUAL_ENV" not in result
    +        assert "CONDA_PREFIX" not in result
    +
    +    def test_sanitize_subprocess_env_strips_markers(self):
    +        from tools.environments.local import _sanitize_subprocess_env
    +        base = {"VIRTUAL_ENV": "/venv", "CONDA_PREFIX": "/conda", "HOME": "/home/user"}
    +        # Even an explicitly-passed extra marker is stripped.
    +        result = _sanitize_subprocess_env(base, {"VIRTUAL_ENV": "/also/venv"})
    +        assert "VIRTUAL_ENV" not in result
    +        assert "CONDA_PREFIX" not in result
    +        assert result.get("HOME") == "/home/user"
    +
    +    def test_markers_constant_contents(self):
    +        from tools.environments.local import _ACTIVE_VENV_MARKER_VARS
    +        assert "VIRTUAL_ENV" in _ACTIVE_VENV_MARKER_VARS
    +        assert "CONDA_PREFIX" in _ACTIVE_VENV_MARKER_VARS
    +
    +
     class TestBlocklistCoverage:
         """Sanity checks that the blocklist covers all known providers."""
     
    ```
  - `tools/environments/local.py`

    ```diff
    @@ -192,6 +192,18 @@ def _build_provider_env_blocklist() -> frozenset:
     
     _HERMES_PROVIDER_ENV_BLOCKLIST = _build_provider_env_blocklist()
     
    +# Active-virtualenv markers that must NOT leak into terminal subprocesses.
    +# The gateway runs inside its own venv, so its process environment carries
    +# VIRTUAL_ENV (and possibly CONDA_PREFIX). If those leak into commands the
    +# agent runs against OTHER Python projects, tools like ``uv``/``poetry`` treat
    +# the inherited value as the active environment and build/sync that other
    +# project's dependencies into the Hermes venv path instead of the project's own
    +# ``.venv`` — silently clobbering the Hermes environment (e.g. a project pinned
    +# to a different Python version overwrites it and breaks the gateway). The
    +# Hermes venv stays reachable via PATH (its bin dir is first), so stripping
    +# these markers is safe and only prevents the cross-project clobber (#23473).
    +_ACTIVE_VENV_MARKER_VARS = ("VIRTUAL_ENV", "CONDA_PREFIX")
    +
     
     def _inject_context_hermes_home(env: dict) -> None:
         """Bridge the context-local Hermes home override into subprocess env."""
    ```

    ```diff
    @@ -232,6 +244,9 @@ def _sanitize_subprocess_env(base_env: dict | None, extra_env: dict | None = Non
         from hermes_constants import apply_subprocess_home_env
         apply_subprocess_home_env(sanitized)
     
    +    for _marker in _ACTIVE_VENV_MARKER_VARS:
    +        sanitized.pop(_marker, None)
    +
         return sanitized
     
     
    ```

    ```diff
    @@ -528,6 +543,9 @@ def _make_run_env(env: dict) -> dict:
         except Exception:
             pass
     
    +    for _marker in _ACTIVE_VENV_MARKER_VARS:
    +        run_env.pop(_marker, None)
    +
         return run_env
     
     
    ```
- **#133868: fix(gateway): stop _ensure_windows_gateway_venv_imports leaking PYTHONPATH into global environment**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `gateway/run.py`, `tests/gateway/test_windows_venv_import_guard.py`
- **#23625: fix(local-env): block virtualenv activation leaks**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#53792`, `#23473`, `#53678`
- **#23905: fix: strip VIRTUAL_ENV from subprocess environment to avoid venv contamination (#23473)**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#23474`, `#23904`, `#23903`
- **#24305: fix(env): Block virtual environment markers from leaking into subprocesses**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#53792`, `#23473`, `#53678`
- **#39593: fix(gateway): prefer installer-canonical venv/ over .venv in detection fallback**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#53792`, `#23473`, `#53678`
- **#41524: fix(desktop): force ad-hoc signing when no real signing identity is configured**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#53792`, `#23473`, `#53678`
- **#45501: fix: cron model.default fallback + VIRTUAL_ENV leak into subprocesses (P1 bugs)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#50011`
- **#57470: fix(gateway): stop _ensure_windows_gateway_venv_imports leaking PYTHONPATH into global environment**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`
- **#57854: fix(desktop): avoid leaking Python path to backend**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`

## Fix status

- Merged to main in PR #53792 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-10-07`.
- Subsystem: `apps/desktop`
