Repository navigation
Conversation
`cron/scheduler_script.py::_script_argv` launches a `.py` job as `[sys.executable, path]`. On a PM-managed install that interpreter is the store Python, whose own site-packages holds only pip, and `build_subprocess_env` strips both Hermes-owned `PYTHONPATH` entries (repo root + the activated dependency environment) before the child spawns. So the first non-stdlib import in a script died with `ModuleNotFoundError` — every config-reading or plugin-importing `no_agent` job failed on POSIX the moment the runtime moved off the in-tree venv, while Windows never regressed because its branch overlays `PYTHONPATH` (and bootstraps `.pth` support). Add the POSIX twin. The interpreter is kept (it already IS the runtime's; only the import path was missing) and the overlay is `[repo root, activated dependency site-packages]`: - the dependency entry is read from this process's own `sys.path`, which is where `pm.environments.activate_dependencies` publishes it (dependencies are exposed by path, `sys.prefix` is deliberately unchanged). No HERMES_HOME filesystem reads on the spawn path, so a process running under an overridden HERMES_HOME never touches the real home. - it is skipped when the interpreter owns its dependencies (wheel/pipx install, dev venv): that already hands them to the child and pinning its own purelib would only reorder `sys.path`. - it is skipped when the directory is not shaped like a venv's own for THIS interpreter version (`<venv>/lib/pythonX.Y/site-packages`), so a generation built for another Python can never be pinned — that would trade `ModuleNotFoundError` for import errors from compiled modules. Tests: `tests/cron/test_cron_script.py` gains an end-to-end run whose dependency exists only in the pinned directory, the argv/overlay contract, and the two skip cases above.
SummaryThis PR fixes a real and well-scoped bug: on a PM-managed POSIX install the cron script child is spawned as a bare The resolver itself is well-guarded. My concerns are all about the shape of the overlay rather than the fix's premise, plus one test-coverage gap that the Windows branch already handles and this one does not. Findings1. Non-blocking: the overlay replaces the user's
|
|
Thanks @nhod (earliest of the fixes for this). This is superseded by #124973, which is armed for rebase-merge. It fixes the same POSIX managed-store cron-script The restart-safe external worker in #122222 is a separate path. #124973 doesn't touch it. |
What does this PR do?
POSIX
.pycron scripts are launched as[sys.executable, path], and on a PM-managed install that interpreter is the store Python (its own site-packages holds only pip) whilebuild_subprocess_envhas just stripped both Hermes-ownedPYTHONPATHentries — the repo root and the activated dependency environment. The child therefore has no importable path to the runtime's third-party dependencies, and a script's first non-stdlib import fails before it does any real work.This adds the POSIX twin of the existing Windows overlay in the same function: keep the interpreter (it is already the runtime's — only the import path was missing) and overlay
[repo root, activated dependency site-packages].The dependency entry is derived from this process's own
sys.path, wherepm.environments.activate_dependenciespublishes it (dependencies are exposed by path;sys.prefixis deliberately unchanged). That means no HERMES_HOME filesystem reads on the spawn path — a process running under an overriddenHERMES_HOMEnever touches the real home — and it is version-guarded by the venv layout (<venv>/lib/pythonX.Y/site-packages) so a generation built for another interpreter is never pinned (the ABI concern from #122395). When the interpreter owns its dependencies (wheel/pipx install, dev venv) nothing is pinned: it already hands them to the child.Related Issue
Refs #122222, #122529 — same class, different spawn site.
Those reports (and the in-flight PRs #122238 / #122290 to
cron/scheduler_worker_env.py) cover the restart-safe external worker ([sys.executable, "-m", "cron.scheduler"]). This PR covers the script runner used byno_agentjobs, which is a separatePopensite incron/scheduler_script.pyand needs its own overlay even after the worker seam is fixed: the worker's pinned entries are Hermes-owned, so the sanitizer strips them again for the script child.Observed live on macOS arm64 (Hermes
main@59004a6235, self-managed install, store Python 3.14.7): the morning after the runtime moved off the in-tree venv, two jobs died in the same tick —cannot load honcho oauth plugin …: No module named 'ruamel'ModuleNotFoundError: No module named 'yaml'(the second also because PyYAML is not a runtime dependency at all — Hermes parses YAML with ruamel via
hermes_yaml). Chat/voice were unaffected; only scheduled script jobs died.Type of Change
Changes Made
cron/scheduler_script.py_script_argv(): the non-Windows branch now picks up_posix_cron_python_invocation()and returns itsenv_overlayinstead of an empty one._posix_cron_python_invocation(): new — POSIX twin of_windows_cron_python_invocation(), overlayingPYTHONPATHon the sanitized child env._activated_dependency_site_packages(): new — resolves the dependency site-packages this process actually runs on fromsys.path, skipping the interpreter's own purelib and non-matching interpreter versions.tests/cron/test_cron_script.py: three tests (all@pytest.mark.platforms("linux", "macos"), matching the repo's host-marker rule):_run_job_scriptrun whose dependency exists only in the pinned directory (fails without the fix),How to Test
scripts/run_tests.sh tests/cron/test_cron_script.py -q→34 passed, 1 skipped(Windows bootstrap tests skip on POSIX hosts); the same file is31 passedonmain._script_argv()handed the child exactly that shape (repo root only, before this change: nothing at all).hermes -p <profile> cron run <no_agent job id>on a job whose script imports any runtime dependency →Ran now: succeeded, and the job'slast_statusflips fromerrortook. Verified here on two real jobs (theruamelandyamlfailures above) plus the hourly reconciler that was already working.Checklist
Code
cron/scheduler_script.py)pytest tests/ -qand all tests pass — partial:tests/cron/test_cron_script.pypasses 34/34 here; a fulltests/cronrun on this host shows 83 pre-existing failures unrelated to this change (same files, same counts with the fix reverted — this is a runtime install without a provisioned test environment, as [Bug]: cron external worker cannot import dependencies on self-managed installs — every scheduled job fails before its ownership ack #122222's reporter also hit). CI's clean environment is the authority.Notes for reviewers
.pthbootstrap on POSIX (site.addsitedir): the overlay only needs to make runtime modules importable, and the editable-install finder is already satisfied because the repo root comes first on the overlay. If maintainers prefer one shared helper for both child kinds,_activated_dependency_site_packages()is written to be reusable fromcron/scheduler_worker_env.pyand would cover the worker seam without the home-scoped reads that PR discussion flagged.sys.executablechildren on a store-Python install is larger ([Bug]: cron external worker cannot import dependencies on self-managed installs — every scheduled job fails before its ownership ack #122222, [Bug]: bot-to-bot DM delivery runner inherits store python with no third-party deps (ruamel death) #122490, kanban dispatcher: worker spawn argv guard checks importability in parent, unsound for bare-interpreter child (ModuleNotFoundError: hermes_cli) #122299), and the umbrella is daemons: every service/spawn/generator path hardcodespython -m hermes_cli.main; resolve the console script instead (+ launchd plist hardening) #76705.