fix(backend): isolate PYTHONPATH to prevent pollution of external projects (ACS-251) (#1065)
* fix(backend): isolate PYTHONPATH to prevent pollution of external projects (ACS-251) When Auto-Claude's backend runs (using Python 3.12), it inherits a PYTHONPATH environment variable that can cause cryptic failures when agents work on external projects requiring different Python versions. The Claude Agent SDK merges os.environ with the env dict we provide when spawning subprocesses. This means any PYTHONPATH set in the parent process is inherited by agent subprocesses, causing import failures and version mismatches in external projects. Solution: - Explicitly set PYTHONPATH to empty string in get_sdk_env_vars() - This overrides any inherited PYTHONPATH from the parent process - Agent subprocesses now have clean Python environments This fixes the root cause of ACS-251, removing the need for workarounds in agent prompt files. Refs: ACS-251 * test: address PR review feedback on test_auth.py - Remove unused 'os' import - Remove redundant sys.path setup (already in conftest.py) - Fix misleading test name: returns_empty_dict -> pythonpath_is_always_set_in_result - Move platform import inside test functions to avoid mid-file imports - Make Windows tests platform-independent using platform.system() mocks - Add new test for non-Windows platforms (test_on_non_windows_git_bash_not_added) All 9 tests now pass on all platforms. Addresses review comments on PR #1065 * test: remove unused pytest import and unnecessary mock - Remove unused 'pytest' import (not used in test file) - Remove unnecessary _find_git_bash_path mock in test_on_non_windows_git_bash_not_added (when platform.system() returns "Linux", _find_git_bash_path is never called) All 9 tests still pass. Addresses additional review comments on PR #1065 * test: address Auto Claude PR Review feedback on test_auth.py - Add shebang line (#!/usr/bin/env python3) - Move imports to module level (platform, get_sdk_env_vars) - Remove duplicate test (test_empty_pythonpath_overrides_parent_value) - Remove unnecessary conftest.py comment - Apply ruff formatting Addresses LOW priority findings from Auto Claude PR Review. --------- Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
This commit is contained in:
@@ -321,6 +321,16 @@ def get_sdk_env_vars() -> dict[str, str]:
|
||||
if bash_path:
|
||||
env["CLAUDE_CODE_GIT_BASH_PATH"] = bash_path
|
||||
|
||||
# Explicitly unset PYTHONPATH in SDK subprocess environment to prevent
|
||||
# pollution of agent subprocess environments. This fixes ACS-251 where
|
||||
# external projects with different Python versions would fail due to
|
||||
# inheriting Auto-Claude's PYTHONPATH (which points to Python 3.12 packages).
|
||||
#
|
||||
# The SDK merges os.environ with the env dict we provide, so setting
|
||||
# PYTHONPATH to an empty string here overrides any inherited value.
|
||||
# The empty string ensures Python doesn't add any extra paths to sys.path.
|
||||
env["PYTHONPATH"] = ""
|
||||
|
||||
return env
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for core/auth module.
|
||||
|
||||
Tests authentication token management and SDK environment variable handling,
|
||||
including the PYTHONPATH isolation fix for ACS-251.
|
||||
"""
|
||||
|
||||
import platform
|
||||
from unittest.mock import patch
|
||||
|
||||
from core.auth import get_sdk_env_vars
|
||||
|
||||
|
||||
class TestGetSdkEnvVars:
|
||||
"""Tests for get_sdk_env_vars() function."""
|
||||
|
||||
def test_pythonpath_is_always_set_in_result(self, monkeypatch):
|
||||
"""
|
||||
PYTHONPATH should always be present in result, even when empty.
|
||||
|
||||
When no SDK env vars are set, PYTHONPATH is still explicitly set to
|
||||
empty string to override any inherited value from the parent process.
|
||||
"""
|
||||
# Clear all SDK env vars
|
||||
for var in [
|
||||
"ANTHROPIC_BASE_URL",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"ANTHROPIC_MODEL",
|
||||
"NO_PROXY",
|
||||
"DISABLE_TELEMETRY",
|
||||
]:
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
# PYTHONPATH should always be present, even if empty
|
||||
assert "PYTHONPATH" in result
|
||||
assert result["PYTHONPATH"] == ""
|
||||
|
||||
def test_includes_anthropic_base_url_when_set(self, monkeypatch):
|
||||
"""Should include ANTHROPIC_BASE_URL when set."""
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", "https://api.example.com")
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
assert result.get("ANTHROPIC_BASE_URL") == "https://api.example.com"
|
||||
|
||||
def test_includes_anthropic_auth_token_when_set(self, monkeypatch):
|
||||
"""Should include ANTHROPIC_AUTH_TOKEN when set."""
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "sk-test-token")
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
assert result.get("ANTHROPIC_AUTH_TOKEN") == "sk-test-token"
|
||||
|
||||
def test_pythonpath_isolation_from_parent_process(self, monkeypatch):
|
||||
"""
|
||||
Test ACS-251 fix: PYTHONPATH from parent process should be overridden.
|
||||
|
||||
This ensures that Auto-Claude's PYTHONPATH (which may point to Python 3.12
|
||||
packages) doesn't pollute agent subprocess environments, preventing
|
||||
failures when working on external projects with different Python versions.
|
||||
"""
|
||||
# Simulate parent process having a PYTHONPATH set
|
||||
monkeypatch.setenv(
|
||||
"PYTHONPATH",
|
||||
"/path/to/auto-claude/backend:/path/to/python3.12/site-packages",
|
||||
)
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
# PYTHONPATH should be explicitly overridden to empty string
|
||||
# This prevents the SDK from inheriting the parent's PYTHONPATH
|
||||
assert "PYTHONPATH" in result
|
||||
assert result["PYTHONPATH"] == ""
|
||||
|
||||
def test_skips_empty_env_vars(self, monkeypatch):
|
||||
"""Should not include env vars with empty values."""
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", "")
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "")
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
# Empty vars should not be in result (except PYTHONPATH which is explicitly set)
|
||||
assert "ANTHROPIC_BASE_URL" not in result
|
||||
assert "ANTHROPIC_AUTH_TOKEN" not in result
|
||||
# PYTHONPATH should still be present as explicit override
|
||||
assert result["PYTHONPATH"] == ""
|
||||
|
||||
def test_on_windows_auto_detects_git_bash_path(self, monkeypatch):
|
||||
"""On Windows, should auto-detect git-bash path if not set."""
|
||||
# Mock platform.system to simulate Windows for cross-platform testing
|
||||
with patch.object(platform, "system", return_value="Windows"):
|
||||
# Mock _find_git_bash_path to return a path
|
||||
with patch(
|
||||
"core.auth._find_git_bash_path",
|
||||
return_value="C:/Program Files/Git/bin/bash.exe",
|
||||
):
|
||||
monkeypatch.delenv("CLAUDE_CODE_GIT_BASH_PATH", raising=False)
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
assert (
|
||||
result.get("CLAUDE_CODE_GIT_BASH_PATH")
|
||||
== "C:/Program Files/Git/bin/bash.exe"
|
||||
)
|
||||
|
||||
def test_preserves_existing_git_bash_path_on_windows(self, monkeypatch):
|
||||
"""On Windows, should preserve existing CLAUDE_CODE_GIT_BASH_PATH."""
|
||||
# Mock platform.system to simulate Windows for cross-platform testing
|
||||
with patch.object(platform, "system", return_value="Windows"):
|
||||
monkeypatch.setenv("CLAUDE_CODE_GIT_BASH_PATH", "C:/Custom/bash.exe")
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
assert result.get("CLAUDE_CODE_GIT_BASH_PATH") == "C:/Custom/bash.exe"
|
||||
|
||||
def test_on_non_windows_git_bash_not_added(self, monkeypatch):
|
||||
"""On non-Windows platforms, CLAUDE_CODE_GIT_BASH_PATH should not be auto-added."""
|
||||
# Mock platform.system to simulate Linux for cross-platform testing
|
||||
# When platform is not Windows, _find_git_bash_path is never called
|
||||
with patch.object(platform, "system", return_value="Linux"):
|
||||
monkeypatch.delenv("CLAUDE_CODE_GIT_BASH_PATH", raising=False)
|
||||
|
||||
result = get_sdk_env_vars()
|
||||
|
||||
# Should not have CLAUDE_CODE_GIT_BASH_PATH
|
||||
assert "CLAUDE_CODE_GIT_BASH_PATH" not in result
|
||||
Reference in New Issue
Block a user