Skip to content

Cron .py scripts import Hermes and its dependencies again on POSIX managed-store installs (#123044, #123440, salvage #123051) - #124973

Merged
kshitijk4poor merged 6 commits into
NousResearch:mainfrom
kshitijk4poor:fix/cron-posix-venv-scripts-123051
Sep 27, 2026
Merged

kshitijk4poor merged 6 commits into
NousResearch:mainfrom
kshitijk4poor:fix/cron-posix-venv-scripts-123051

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Sep 27, 2026 •

Copy link
Copy Markdown

Cron .py job scripts can import Hermes and its dependencies again on POSIX managed-store installs, and they load the live checkout rather than a stale copy (fixes #123044 and #123440; salvages #123051 by @liuhao1024).

WHO/WHEN: macOS/Linux users on 0.21.5+ managed-store (PM) installs whose cron jobs run a Python script/prerun. WHY: the gateway runs on the bare store Python, whose repo + dependency paths exist only in-process, so a script it spawned died with ModuleNotFoundError: No module named 'cron' (or mcp, ruamel, google, ...).

Changes

  • POSIX managed-store installs (@liuhao1024, cherry-picked): run the script on the selected dependency venv's own interpreter, so pyvenv.cfg resolves its site-packages. No PYTHONPATH overlay. Every child the script spawns would inherit one, and a foreign interpreter would then load the store's compiled extensions (Cron .py scripts can't import Hermes modules or managed dependencies on macOS PM-managed installs (0.21.5) #123440).
  • Live checkout first: the venv resolves Hermes from its generation's workspace snapshot, which pm/packages.py::expected_stamp rebuilds only when uv.lock, extras, the Python version or plugins change. After a code-only update, scripts imported older Hermes code than the gateway was running (on the test machine, 26 files in tools/ differed). A python -c bootstrap now puts the live checkout right after the script's directory, in-process only. It keeps python script.py semantics: a real __main__ module that survives into atexit handlers, sys.path[0] set to the script's directory, and -P respected.
  • Lazy installs stay off for the script's process tree (HERMES_DISABLE_LAZY_INSTALLS=1). A script importing hermes_bootstrap could otherwise complete a source update and execv itself onto the bare store Python via sys.orig_argv. This also reaches a hermes … command the script shells out to: lazy installs are refused there.
  • Broken PM records fail the run. _script_argv now runs inside _run_job_script's try. A corrupt store manifest or a bad selection record used to escape the runner and strand the execution row, on the existing Windows committed_venv path too. They now report Script execution failed: <PM error>. The contributor's second commit made this case fall back to the caller's interpreter; that is reversed, because on this path the caller's interpreter is the bare store Python, i.e. the original bug.
  • Unchanged:
    • A selected venv whose interpreter is missing (a half-deleted venv) also fails the run, naming the missing path, instead of silently running on the bare store Python.
    • Installs without a store, and Windows, behave as before.
  • Tests:
    • Two invariant tests.
    • tests/cron now points HERMES_RUNTIME_DIR at an empty directory, so script tests don't read the host's PM store.

Validation

before after
Live script run through _run_job_script, real PM install (macOS) ModuleNotFoundError: No module named 'cron' succeeds; cron from the live checkout; child and grandchild PYTHONPATH unset
Bootstrap vs python script.py on the real venv (spawn Pool, sys.exit(3), traceback, pickle incl. atexit, sibling import, -P, non-ASCII path) — identical (traceback gains one bootstrap frame)
New tests on main / with a mutation removing each part of the fix — both red on main; each mutation turns its test red
tests/cron + test_cron_windows_venv_abi + test_windows_native_support — green on the targeted files. Full tests/cron: same 13 failing files and counts as main on the same host (pre-existing home-guard failures from a checkout inside the real hermes home)

Closes #123044. Closes #123440. Closes #123547.

Also supersedes the script-side fixes in #122742 (@nhod, earliest), #123095 (@gumclaw), #123479 (@webtecnica), #123548 (@vladneshrus-cpu), and the script half of #123525 (@Sahilvishnaliya). The restart-safe external worker (#122222 / #123400, #123738 and the worker half of #123525) is a separate path and is not changed here.

liuhao1024 and others added 5 commits September 27, 2026 14:03
On managed-store installs the gateway's store Python carries the repo and
the managed site-packages only in-process; cron .py scripts spawned with
sys.executable re-resolve imports from scratch and die with
ModuleNotFoundError (NousResearch#123044). Run them on the selected dependency venv's
interpreter instead — its pyvenv.cfg site-packages (editable installs
included) need no PYTHONPATH overlay, so children the script spawns never
inherit the store's paths and import CPython-3.14 extension modules on a
foreign interpreter (NousResearch#123440). Lazy installs are disabled for script
children so hermes_bootstrap imports off the store-record venv cannot
republish launchers. The .pth bootstrap routing in _script_argv is
tightened to PYTHONPATH-carrying overlays, which the POSIX path no
longer produces.

(cherry picked from commit 2d6976c)
_posix_cron_python_invocation called selected_venv(repo) unguarded; the
four RuntimeError cases it is documented to raise would escape through
_script_argv (which runs before _run_job_script's try), crash the tick,
and leave the execution row in running forever — the exact half-migrated
install the test docstring already claimed was supported. Mirror the
Windows bootstrap's degrade-don't-crash contract: warn and run on the
caller's interpreter. Adds the raising-selection case to the POSIX
invocation test (red on the previous head), a sealed-payload caveat on
the e2e test's hand-written .pth, and a pointer from the Windows
invocation docstring to its POSIX counterpart.

(cherry picked from commit 8d5b3b5)
…ection fails the run

Follow-up to the venv-interpreter change above.

- The selected venv resolves Hermes from its generation's workspace
  snapshot, which only a dependency change (uv.lock / extras / Python /
  plugins, pm/packages.py::expected_stamp) rebuilds. After a code-only
  update, scripts imported older Hermes code than the gateway runs. A
  `python -c` bootstrap now puts the live checkout right after the
  script's directory, in-process, so nothing is inherited by the
  script's children (no PYTHONPATH, NousResearch#123440).
- POSIX dispatch moves into _script_argv (platform branch), so
  _windows_cron_python_invocation is Windows-only again and the
  PYTHONPATH-keyed bootstrap gate goes back to its original form.
- The interpreter path comes from pm.environments.venv_python.
- _script_argv now runs inside _run_job_script's try. PM record reads
  (store manifest, facts, selection) can raise ValueError/KeyError as
  well as RuntimeError; before, those escaped the runner and stranded
  the execution row, and on POSIX the local fallback handed the script
  to the bare store interpreter (the NousResearch#123044 symptom). A broken
  selection is now a failed run with the PM error, per
  selected_venv's contract. This also covers the same pre-existing
  gap on the Windows committed_venv path.
- Tests: two invariant tests replace the three change-detector tests
  (live checkout beats a snapshot on sys.path, venv site-packages
  resolve, script-dir sys.path[0], no PYTHONPATH; broken selection
  fails the run instead of escaping).
POSIX cron scripts now consult the managed store. Without isolation, a
test run from a PM-managed checkout (the standard ~/.hermes/hermes-agent)
reads the host's store and install records, trips the home I/O guard,
and would run scripts on the host's dependency venv. Point
HERMES_RUNTIME_DIR at an empty per-test dir for tests/cron.
Review follow-up on the live-checkout bootstrap.

- runpy.run_path runs the script under a temporary __main__ that is
  swapped back once the body returns, so atexit handlers or non-daemon
  threads that pickle script-defined classes failed ("not found as
  __main__.Foo") where a plain `python script.py` works. The bootstrap
  now installs a real __main__ module (SourceFileLoader, __cached__ as
  in a plain run) and execs the compiled script in it. Tracebacks keep
  one bootstrap frame instead of runpy's three.
- Under -P / PYTHONSAFEPATH there is no cwd entry to replace, so the
  repo is prepended instead of overwriting a stdlib path.
- Interpreter via pm.environments.project_python (the existing
  venv_python(selected_venv()) helper).
- A missing venv interpreter still falls back to the caller's Python
  but now logs a warning naming it, like the Windows bootstrap fallback.
- Docstrings: the lazy-install gate's real reason (a script importing
  hermes_bootstrap could complete a source update and execv itself onto
  the bare store Python via sys.orig_argv); _script_argv no longer
  claims "else sys.executable".
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cron Cron scheduler and job management comp/cli CLI entry point, hermes_cli/, setup wizard area/install-update Installer, updater, packaging, wheels, doctor needs-decision Awaiting maintainer decision before any implementation sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 27, 2026
_posix_cron_script_argv fell back to sys.executable when the selected venv's
interpreter was gone. On a managed-store install that is the bare store Python,
so the run logged a warning and then died with the same ModuleNotFoundError as
NousResearch#123044. A half-deleted venv reaches this branch because _recorded_venv only
checks pyvenv.cfg. Raise instead; _run_job_script's try reports it as a failed
run naming the missing interpreter, matching how a broken PM record is handled.
The existing broken-selection test is parametrized over both causes.
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) September 27, 2026 08:54
@kshitijk4poor
kshitijk4poor merged commit 009699b into NousResearch:main Sep 27, 2026
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

3 participants