From 6ff39c31add9113469274e4093b8f66bb2a264f1 Mon Sep 17 00:00:00 2001 From: ethernet Date: Fri, 12 Jun 2026 01:19:36 -0400 Subject: [PATCH] fix(tests): guard against real 'hermes update' subprocess spawns in conftest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends _live_system_guard in tests/conftest.py to block any subprocess call that would run 'hermes update' (or 'python -m hermes_cli.main update') against the real checkout. These commands run git fetch origin + git pull, overwriting repo files like pyproject.toml mid-test-run and corrupting every subsequent subprocess that reads them. The spawned process uses setsid / start_new_session=True so it's invisible to pytest's process tree (PPid=1) — the corruption was essentially undetectable without explicit inotify/SHA watchdogs. Root cause of #43703 CI failures: tests in TestUpdateCommandPlatformGate called _handle_update_command() with HERMES_MANAGED='' and no Popen mock, causing the code to fall through and spawn a real 'hermes update --gateway' that overwrote pyproject.toml with origin/main's content (which still had '--timeout=30 --timeout-method=thread' in addopts while the PR had already removed pytest-timeout). The guard covers all three invocation patterns: - 'hermes update' / 'hermes update --gateway' (direct or via setsid bash -c) - 'python -m hermes_cli.main update --gateway' - '.venv/bin/hermes update' (absolute path variant) Does not false-positive on: git update-index, apt-get update, pip install --upgrade, or any command lacking 'hermes'/'hermes_cli'. --- tests/conftest.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 8e1a8dfb9c0..2da7d4a1eb4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -731,6 +731,41 @@ def _live_system_guard(request, monkeypatch): "Mark with @pytest.mark.live_system_guard_bypass if " "intentional." ) + # Block any subprocess that would run `hermes update` (or the + # equivalent `python -m hermes_cli.main update`). These commands + # run `git fetch origin + git pull` against the REAL checkout, + # overwriting files like pyproject.toml mid-test-run and corrupting + # every subsequent subprocess that reads them. The corruption is + # especially insidious because the spawned process uses setsid/ + # start_new_session=True, making it invisible to pytest's process + # tree (PPid=1) and nearly impossible to trace without explicit + # inotify/SHA watchdogs. Any test that legitimately needs to exercise + # the update-spawn path must mock subprocess.Popen explicitly. + cmd_str = _cmd_to_string(cmd) + low = cmd_str.lower() + if "update" in low and ( + # hermes update / hermes update --gateway / setsid bash -c ... hermes update + ("hermes" in low and "update" in low.split()) + or + # python -m hermes_cli.main update --gateway + ("hermes_cli" in low and "update" in low.split()) + or + # venv/bin/hermes update (absolute path variant used in tests) + (".venv/bin/hermes" in low and "update" in low) + ): + raise RuntimeError( + f"tests/conftest.py live-system guard: blocked " + f"subprocess.{name}({cmd!r}) — this command would run " + "`hermes update` against the real checkout, fetching " + "from origin and overwriting repo files (e.g. " + "pyproject.toml) mid-test-run. This corrupts every " + "subsequent subprocess in the same runner. " + "Mock subprocess.Popen (and subprocess.run if used) " + "in the test instead, or mark with " + "@pytest.mark.live_system_guard_bypass if genuinely " + "needed (e.g. an integration test testing the update " + "flow against a dedicated throwaway repo)." + ) def _wrap_subprocess(name, real): def _guarded(cmd, *args, **kwargs):