fix(langgraph): prefer executable in venv over path (#341)

This commit is contained in:
dinos
2026-07-11 13:59:27 +02:00
committed by GitHub
parent 6f10406d5b
commit 49770949da
2 changed files with 83 additions and 5 deletions
+11 -5
View File
@@ -306,14 +306,19 @@ def is_async_subagents_available() -> bool:
def _langgraph_exe() -> str | None:
"""Return the path to the langgraph CLI binary, or None if not found."""
import sys
executable_dir = os.path.dirname(sys.executable)
candidate_names = (
["langgraph.exe", "langgraph"] if os.name == "nt" else ["langgraph"]
)
for candidate_name in candidate_names:
candidate = os.path.join(executable_dir, candidate_name)
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
found = shutil.which("langgraph")
if found:
return found
import sys as _sys
candidate = os.path.join(os.path.dirname(_sys.executable), "langgraph")
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
return None
@@ -709,6 +714,7 @@ def start_langgraph_dev(
sub_env["EVOSCIENTIST_DEPLOY_MODE"] = "full" if deploy_mode else "stripped"
try:
logger.info("Starting langgraph dev with CLI: %s", exe)
proc = subprocess.Popen(
[
exe,
+72
View File
@@ -8,6 +8,7 @@ to be available.
from __future__ import annotations
import dataclasses
import sys
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
@@ -32,6 +33,77 @@ def reset_module_state():
manager._LOG_OFFSET_AT_START = 0
# =============================================================================
# langgraph CLI resolution
# =============================================================================
class TestLanggraphCliResolution:
def _make_executable(self, path):
path.write_text("#!/bin/sh\n", encoding="utf-8")
path.chmod(0o755)
def test_prefers_current_python_environment_over_path(self, tmp_path, monkeypatch):
local_bin = tmp_path / "local" / "bin"
local_bin.mkdir(parents=True)
local_langgraph = local_bin / "langgraph"
self._make_executable(local_langgraph)
path_bin = tmp_path / "path" / "bin"
path_bin.mkdir(parents=True)
path_langgraph = path_bin / "langgraph"
self._make_executable(path_langgraph)
monkeypatch.setattr(sys, "executable", str(local_bin / "python"))
monkeypatch.setattr(
manager.shutil,
"which",
lambda command: str(path_langgraph) if command == "langgraph" else None,
)
assert manager._langgraph_exe() == str(local_langgraph)
def test_falls_back_to_path_when_environment_binary_missing(
self, tmp_path, monkeypatch
):
path_bin = tmp_path / "path" / "bin"
path_bin.mkdir(parents=True)
path_langgraph = path_bin / "langgraph"
self._make_executable(path_langgraph)
monkeypatch.setattr(
sys, "executable", str(tmp_path / "local" / "bin" / "python")
)
monkeypatch.setattr(
manager.shutil,
"which",
lambda command: str(path_langgraph) if command == "langgraph" else None,
)
assert manager._langgraph_exe() == str(path_langgraph)
def test_checks_windows_suffix_next_to_current_python(self, tmp_path, monkeypatch):
scripts_dir = tmp_path / "Scripts"
scripts_dir.mkdir()
local_langgraph = scripts_dir / "langgraph.exe"
self._make_executable(local_langgraph)
path_bin = tmp_path / "path" / "bin"
path_bin.mkdir(parents=True)
path_langgraph = path_bin / "langgraph.exe"
self._make_executable(path_langgraph)
monkeypatch.setattr(sys, "executable", str(scripts_dir / "python.exe"))
monkeypatch.setattr(manager.os, "name", "nt", raising=False)
monkeypatch.setattr(
manager.shutil,
"which",
lambda command: str(path_langgraph) if command == "langgraph" else None,
)
assert manager._langgraph_exe() == str(local_langgraph)
# =============================================================================
# is_langgraph_dev_running
# =============================================================================