The pin landed inline in the cron/scheduler.py facade and rebuilt PYTHONPATH from raw os.environ. That resurrected the Hermes-owned entries build_subprocess_env had just stripped (runtime site-packages, launcher spellings of the repo root) instead of extending what the sanitizer kept. It also ran unconditionally: under a wheel / pipx / uv-tool install `Path(__file__).parent.parent` IS purelib, so the pin hoisted site-packages above the stdlib on the worker's sys.path instead of being a no-op. cron/scheduler_worker_env.py::pin_hermes_tree_on_pythonpath prepends repo_root to worker_env's own PYTHONPATH and returns the env untouched when repo_root is sysconfig purelib (cron/ is already importable there). Test: A/B with the previous inline pin -> the raw-environ-only entry leaked into the spawn env. Part of #112729: this hardens the PYTHONSAFEPATH / cwd-not-checkout case; the reporter's failure did not reproduce on main from cwd=checkout, so the real worker stderr tail (now captured bye094e25b26/f857a4ed99) is still needed.
43 lines
1.8 KiB
Python
43 lines
1.8 KiB
Python
"""Cron: import path of the restart-safe external worker.
|
|
|
|
The worker is spawned as ``sys.executable -m cron.scheduler``. Its entry module is
|
|
``cron.scheduler``, not ``hermes_cli.main``, so nothing bootstraps the gateway's checkout
|
|
onto its ``sys.path``; historically it imported ``cron`` only through the implicit ``-m``
|
|
cwd entry. That entry is gone under ``PYTHONSAFEPATH`` and useless when the venv's
|
|
editable install maps a moved/deleted checkout -- the worker then dies with
|
|
"No module named 'cron'" before its ownership ack (#112729, hypothesised cause).
|
|
|
|
The shared subprocess sanitizer strips Hermes-owned PYTHONPATH entries because user
|
|
children must not see our tree. This child IS Hermes, so the pin is applied *after* the
|
|
env is built, on the sanitized env -- the sanitizer's other decisions (dropped runtime
|
|
site-packages, dropped venv markers) stand.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sysconfig
|
|
from pathlib import Path
|
|
|
|
|
|
def _installed_purelib() -> Path | None:
|
|
try:
|
|
return Path(sysconfig.get_paths()["purelib"]).resolve()
|
|
except (KeyError, OSError):
|
|
return None
|
|
|
|
|
|
def pin_hermes_tree_on_pythonpath(worker_env: dict, repo_root: Path) -> dict:
|
|
"""Prepend ``repo_root`` to the worker env's own PYTHONPATH (never ``os.environ``'s).
|
|
|
|
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``.
|
|
"""
|
|
root = str(repo_root)
|
|
if _installed_purelib() == Path(root).resolve():
|
|
return 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]))
|
|
return worker_env
|