Files
hermes-agent/tests/agent/lsp/test_python_discovery.py
John Paul Soliva f5c754d436 fix(lsp): never run a cloned repository's own interpreter or TypeScript SDK in an untrusted workspace
Language servers start with no approval on every write_file/patch inside any
git worktree. Hermes pointed pyright at the checkout's own .venv/venv python
(pyright executes it), let Vue and typescript-language-server load the
checkout's node_modules/typescript, and left svelte-language-server and
rust-analyzer loading project config, build scripts and proc-macros.

A workspace is now trusted only when it is the git worktree Hermes was
launched in or sits under an lsp.trusted_workspaces entry. Elsewhere pyright
keeps VIRTUAL_ENV or the Hermes-managed python, TypeScript/Vue are pinned to
the Hermes-side SDK, svelte gets isTrusted:false and rust-analyzer runs
without build scripts or proc-macros.
2026-09-29 03:28:46 +05:30

79 lines
3.1 KiB
Python

"""Pyright uses the project environment before the Hermes runtime fallback."""
import os
import subprocess
import sys
from pathlib import Path
from pm import paths
from pm.lock import Facts, Lockfile
from pm.registry import get_package
from pm.store import current_target
def _seed_pm_python(tmp_path, monkeypatch):
"""Stage a pm bundled-install layout: HERMES_RUNTIME_DIR -> store with a
manifest sibling (bundled), a python entry, and facts recording it."""
payload = tmp_path / "payload"
store = payload / "tools"
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home"))
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(store))
lock = Lockfile(paths.lockfile_path())
package = get_package("python")
target = current_target()
version = lock.version("python")
assert version is not None
python_entry = store / package.store_entry(version, target)
python_entry.mkdir(parents=True)
exe = package.binary(python_entry, target)
assert exe is not None
exe.parent.mkdir(parents=True, exist_ok=True)
exe.write_text("", encoding="utf-8")
(payload / "manifest.json").write_text("{}", encoding="utf-8")
Facts(paths.facts_path()).record(
"python", version, python_entry.name, package.env(python_entry, target), store,
target=target, artifacts=[a["sha256"] for a in lock.artifacts("python", target)],
)
return exe
def test_pyright_uses_the_project_interpreter_before_hermes(tmp_path, monkeypatch):
from agent.lsp import servers
project = tmp_path / "project"
project.mkdir()
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
pm_python = _seed_pm_python(tmp_path, monkeypatch)
context = servers.ServerContext(
workspace_root=str(project), install_strategy="off",
binary_overrides={"pyright": [sys.executable]}, trusted=True,
)
server = servers.find_server_for_file(str(project / "app.py"))
assert server is not None
spec = server.build_spawn(str(project), context)
assert spec is not None
assert spec.initialization_options["python"]["pythonPath"] == str(pm_python)
pm_python.unlink()
spec = server.build_spawn(str(project), context)
assert spec is not None
assert "pythonPath" not in spec.initialization_options.get("python", {})
for environment in (project / ".venv", tmp_path / "explicit-environment"):
subprocess.run(
[sys.executable, "-m", "venv", "--without-pip", str(environment)],
check=True, capture_output=True, text=True, timeout=30,
)
python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
if environment.name == "explicit-environment":
monkeypatch.setenv("VIRTUAL_ENV", str(environment))
spec = server.build_spawn(str(project), context)
assert spec is not None
selected = spec.initialization_options["python"]["pythonPath"]
assert Path(selected) == python
child = subprocess.run(
[selected, "-I", "-c", "import sys; print(sys.prefix)"],
check=True, capture_output=True, text=True, timeout=10,
)
assert Path(child.stdout.strip()) == environment