From dc4f183c6073b95b4b491dcb4061b0015f3d22de Mon Sep 17 00:00:00 2001 From: Coding Agent Date: Sat, 18 Jul 2026 09:29:36 +0800 Subject: [PATCH] fix: block benchmark agents from global pip --- scripts/compare_three_systems.py | 14 +++++++++++--- swe_agent_local_runner.py | 2 ++ tests/test_swe_agent_local_runner.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/scripts/compare_three_systems.py b/scripts/compare_three_systems.py index 7be9bf5..3068c82 100644 --- a/scripts/compare_three_systems.py +++ b/scripts/compare_three_systems.py @@ -240,9 +240,7 @@ def run_claude( text=True, timeout=120, ) - env = _claude_environment(model) - env["VIRTUAL_ENV"] = str(command_venv) - env["PATH"] = f"{command_venv / 'bin'}{os.pathsep}{env.get('PATH', '')}" + env = _activate_command_venv(_claude_environment(model), command_venv) env["CLAUDE_CODE_DEBUG"] = "1" env["ANTHROPIC_MAX_TOKENS"] = "64000" @@ -318,6 +316,16 @@ def _claude_environment(model: str) -> dict[str, str]: return env +def _activate_command_venv(env: dict[str, str], command_venv: Path) -> dict[str, str]: + """Activate a task venv and reject pip calls that bypass it.""" + activated = dict(env) + activated["VIRTUAL_ENV"] = str(command_venv) + activated["PATH"] = f"{command_venv / 'bin'}{os.pathsep}{activated.get('PATH', '')}" + activated["PIP_REQUIRE_VIRTUALENV"] = "true" + activated["PYTHONNOUSERSITE"] = "1" + return activated + + def preflight_claude_endpoint(model: str) -> None: """Verify Claude's endpoint before any task can be counted.""" completed = subprocess.run( diff --git a/swe_agent_local_runner.py b/swe_agent_local_runner.py index 7a2152d..904f103 100644 --- a/swe_agent_local_runner.py +++ b/swe_agent_local_runner.py @@ -205,6 +205,8 @@ def _start_bash(self) -> None: if self.command_venv is not None: env["VIRTUAL_ENV"] = str(self.command_venv) env["PATH"] = f"{self.command_venv / 'bin'}{os.pathsep}{env.get('PATH', '')}" + env["PIP_REQUIRE_VIRTUALENV"] = "true" + env["PYTHONNOUSERSITE"] = "1" # Use a new session so we can kill the whole process group (bash + # any spawned children) when a command times out. self._proc = subprocess.Popen( diff --git a/tests/test_swe_agent_local_runner.py b/tests/test_swe_agent_local_runner.py index 7cc8a08..b5aa158 100644 --- a/tests/test_swe_agent_local_runner.py +++ b/tests/test_swe_agent_local_runner.py @@ -1,4 +1,5 @@ import json +import os import shlex import sys from pathlib import Path @@ -9,6 +10,7 @@ from agent.config import Config from scripts.compare_three_systems import ( + _activate_command_venv, _claude_environment, build_goal_description, preflight_claude_endpoint, @@ -62,6 +64,8 @@ def test_local_env_uses_per_task_command_venv(tmp_path): assert env.communicate('printf "$VIRTUAL_ENV"') == str(command_venv) path = env.communicate('printf "$PATH"') assert path.split(":")[1] == str(command_venv / "bin") + assert env.communicate('printf "$PIP_REQUIRE_VIRTUALENV"') == "true" + assert env.communicate('printf "$PYTHONNOUSERSITE"') == "1" finally: env.close() @@ -167,6 +171,17 @@ def test_claude_environment_can_use_shared_benchmark_endpoint(monkeypatch): assert env["ANTHROPIC_MODEL"] == "deepseek-v4-flash[1m]" +def test_claude_command_venv_blocks_global_pip(tmp_path): + command_venv = tmp_path / "command_venv" + + env = _activate_command_venv({"PATH": "/usr/bin"}, command_venv) + + assert env["VIRTUAL_ENV"] == str(command_venv) + assert env["PATH"].startswith(f"{command_venv / 'bin'}{os.pathsep}") + assert env["PIP_REQUIRE_VIRTUALENV"] == "true" + assert env["PYTHONNOUSERSITE"] == "1" + + def test_claude_preflight_allows_slow_official_endpoint(monkeypatch): observed = {}