Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion cron/scheduler_worker_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,19 @@ def pin_hermes_tree_on_pythonpath(worker_env: dict, repo_root: Path) -> dict:
Skipped when ``repo_root`` is the interpreter's ``purelib``: under a wheel / pipx /
uv-tool install ``cron/`` lives in site-packages itself, which is already importable,
and pinning it would move site-packages ahead of the stdlib on ``sys.path``.

The sanitizer also strips the runtime's own site-packages (third-party deps such as
``ruamel.yaml``/``dotenv`` that ``cron``/``hermes_yaml``/``hermes_cli.env_loader`` import),
correctly for a genuine user child but not for this one -- this worker IS Hermes, so those
dirs are re-pinned here too, right alongside the repo root (#112729 follow-up: repo root
alone left every third-party import broken for the external cron worker).
"""
root = str(repo_root)
if _installed_purelib() == Path(root).resolve():
return worker_env
from tools.environments.local_pythonpath import _get_hermes_site_packages

site_packages = [str(p) for p in _get_hermes_site_packages(worker_env)]
existing = [e for e in worker_env.get("PYTHONPATH", "").split(os.pathsep) if e]
worker_env["PYTHONPATH"] = os.pathsep.join(dict.fromkeys([root, *existing]))
worker_env["PYTHONPATH"] = os.pathsep.join(dict.fromkeys([root, *site_packages, *existing]))
return worker_env
37 changes: 37 additions & 0 deletions tests/cron/test_scheduler_worker_env_site_packages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Regression for #125999: the cron external worker's PYTHONPATH pin restored the repo
root but not the runtime's own site-packages, so any third-party import on the worker's
bootstrap path (``ruamel.yaml`` via ``hermes_yaml``, ``dotenv`` via
``hermes_cli.env_loader``) died with ModuleNotFoundError before the worker could send its
ownership acknowledgement (#112729 follow-up)."""

from cron.scheduler_worker_env import pin_hermes_tree_on_pythonpath


def test_pin_restores_site_packages_alongside_repo_root(monkeypatch, tmp_path):
repo_root = tmp_path / "repo"
repo_root.mkdir()
site_packages = tmp_path / "venv" / "lib" / "python3.14" / "site-packages"
site_packages.mkdir(parents=True)

monkeypatch.setattr(
"tools.environments.local_pythonpath._get_hermes_site_packages",
lambda env: [site_packages],
)
monkeypatch.setattr("cron.scheduler_worker_env._installed_purelib", lambda: None)

env = pin_hermes_tree_on_pythonpath({"PYTHONPATH": "/existing/user/path"}, repo_root)

entries = env["PYTHONPATH"].split(":")
assert str(repo_root) in entries
assert str(site_packages) in entries
assert "/existing/user/path" in entries


def test_pin_is_a_noop_under_a_purelib_install(monkeypatch, tmp_path):
repo_root = tmp_path / "repo"
repo_root.mkdir()
monkeypatch.setattr("cron.scheduler_worker_env._installed_purelib", lambda: repo_root)

env = pin_hermes_tree_on_pythonpath({}, repo_root)

assert "PYTHONPATH" not in env