Repository navigation
fix(cron): restore activated dependency site-packages in external worker PYTHONPATH - #122238
webtecnica wants to merge 1 commit into
Conversation
|
🤖 Hermes Agent automated review: PR reviewed. Diff analyzed (113 lines changed). Check CI status and manual review recommended. |
arkheioncorp
left a comment
There was a problem hiding this comment.
Review: fix(cron): restore activated dependency site-packages in external worker PYTHONPATH
Verdict: APPROVE
Findings
- Clean fix. The new
_activated_site_packages()helper correctly walkssys.path, skips the interpreter's own purelib, and requires a real venv ancestor (pyvenv.cfg) — no accidental non-venv site-packages leaking in. - Good edge-case handling.
OSError/RuntimeError/ValueErroraroundPath.resolve()are caught; emptysys.pathentries are skipped. - Test coverage.
test_pin_hermes_tree_restores_activated_dependency_site_packagessets up a fake venv withpyvenv.cfg, a stray non-venvsite-packages, and asserts the right ordering — exactly the scenario that motivated the fix. - No security concerns. No secrets, no shell injection, no unsafe deserialization.
One minor note: the existing test test_launch_external_worker_pin_extends_the_sanitized_env_not_os_environ was relaxed from an exact-equality assert to containment checks — this is the right call given the new variable insertion, but worth a glance to ensure the ordering intent (repo_root first, then activated, then existing) is preserved. The new test enforces that ordering explicitly, so it is covered.
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, #122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from #122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: #122238 (@webtecnica, earliest), #122382 (@Sakujakira), #122685 (@etrisnanto), #122719 (@aglowinthefield) and #122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com>
Production verification (self-hosted, PM-managed install)Hit this on a real host: 14 scheduled jobs, every external worker died before its ownership ack since ~Sept 24 (326+ Applied this PR's approach locally (independently derived, nearly identical to
One robustness note for review: this implementation scans Either way — this PR unblocks every PM-era self-hosted install with restart-safe cron workers. Would love to see it merged; happy to re-test on my host after merge. |
|
Can someone please approve this? 🙏🏻😭 |
|
Independent confirmation from a live self-managed (PM / managed-runtime) install — Linux arm64 (Raspberry Pi), v0.21.5 @ 5912ed8, systemd-managed gateway. Symptom before the fix, on every dispatch from the managed gateway (first failure 2026-09-28 09:45, every tick after): Repro of the spawn contract (store Python + checkout pinned, no dependency activation): cd ~/.hermes/hermes-agent
<store-python> -m cron.scheduler --help # ModuleNotFoundError: No module named 'ruamel'
PYTHONPATH="$PWD:<generation-site-packages>" <store-python> -m cron.scheduler --help # rc=0
I had independently implemented a local variant that resolves the path via Test evidence: One edge worth a test if you agree: a |
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, NousResearch#122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from NousResearch#122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: NousResearch#122238 (@webtecnica, earliest), NousResearch#122382 (@Sakujakira), NousResearch#122685 (@etrisnanto), NousResearch#122719 (@aglowinthefield) and NousResearch#122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, NousResearch#122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from NousResearch#122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: NousResearch#122238 (@webtecnica, earliest), NousResearch#122382 (@Sakujakira), NousResearch#122685 (@etrisnanto), NousResearch#122719 (@aglowinthefield) and NousResearch#122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
|
Closing as superseded by #126344, which fixes the same failure: the restart-safe cron external worker dying with Thank you @webtecnica, you're credited in the #126344 body. Yours was the earliest fix submitted for #122222, and the PR body credits it that way. If #126344 stalls, I'll reopen this. |
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, NousResearch#122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from NousResearch#122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: NousResearch#122238 (@webtecnica, earliest), NousResearch#122382 (@Sakujakira), NousResearch#122685 (@etrisnanto), NousResearch#122719 (@aglowinthefield) and NousResearch#122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, #122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from #122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: #122238 (@webtecnica, earliest), #122382 (@Sakujakira), #122685 (@etrisnanto), #122719 (@aglowinthefield) and #122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com>
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, #122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from #122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: #122238 (@webtecnica, earliest), #122382 (@Sakujakira), #122685 (@etrisnanto), #122719 (@aglowinthefield) and #122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com>
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, NousResearch#122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from NousResearch#122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: NousResearch#122238 (@webtecnica, earliest), NousResearch#122382 (@Sakujakira), NousResearch#122685 (@etrisnanto), NousResearch#122719 (@aglowinthefield) and NousResearch#122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, NousResearch#122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from NousResearch#122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: NousResearch#122238 (@webtecnica, earliest), NousResearch#122382 (@Sakujakira), NousResearch#122685 (@etrisnanto), NousResearch#122719 (@aglowinthefield) and NousResearch#122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, #122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from #122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: #122238 (@webtecnica, earliest), #122382 (@Sakujakira), #122685 (@etrisnanto), #122719 (@aglowinthefield) and #122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
The restart-safe cron worker is spawned as `sys.executable -m cron.scheduler`, a Hermes entry point that never goes through `hermes_bootstrap`. On a self-managed (PM) install its interpreter is the store Python, which owns no dependencies and whose PYTHONPATH the subprocess sanitizer has just stripped, so the worker died at its first dependency import (`No module named 'ruamel'`, NousResearch#122222) before it could publish its ownership acknowledgement -- every scheduled job landed `last_status: failed`. Salvaged from NousResearch#122290 (thanks @123456hyu), whose two commits pinned the dependency site-packages and leased the generation at the worker entry. Reworked onto PM's own machinery: * `pin_hermes_tree_on_pythonpath` asks `pm.environments.committed_venv` + `site_packages()` for the generation, instead of deriving one from this process's `sys.path` behind a `pyvenv.cfg` ancestor probe. The record is scoped to the active home, so the child-spawn path no longer reads the real home -- that probe tripped `tests/home_io_guard.py` when the suite runs from a self-managed install -- and the uv-launcher, first-match-only and ordering edge cases of the old derivation go with it. * `cron/worker_bootstrap.py` is now the PM call the worker actually needs: lease + `site.addsitedir` + `sys.path`/`PYTHONPATH`/`PATH` are all `pm.environments.activate_dependencies`, the same boot `hermes_bootstrap` runs, and it holds the generation's lease for the process's life. It is gated on the `HERMES_CRON_EXTERNAL_WORKER` marker the spawn site sets (consumed on read) rather than sniffing `--external-worker-file` out of `sys.argv`, so the gateway keeps its launch contract without argv identity inference. Verified live on PM's store Python against a real generation (A/B, same child): tree-only PYTHONPATH -> `ModuleNotFoundError: No module named 'ruamel'`, rc=1; pinned env -> imports, `running_from_selected_environment` True, and the worker holds a real kernel lease while it runs (released on exit). The real `-m cron.scheduler --external-worker-file ...` entry now reaches its ownership handoff instead of dying at the import. `tests/cron/`: 1376 passed. Tests: the two invariants that carry the contract -- the pin restores the committed generation (and invents nothing without one), and the boot runs only in the marked worker, once. The six construction tests that drove the old derivation are dropped with it. Duplicate-cluster credit: NousResearch#122238 (@webtecnica, earliest), NousResearch#122382 (@Sakujakira), NousResearch#122685 (@etrisnanto), NousResearch#122719 (@aglowinthefield) and NousResearch#122738 (@GabboAlarcon) diagnosed and fixed this same worker-dependency failure in parallel while this branch was rebuilt. None of their diffs is contained here -- the co-author trailers below record direction and duplicate-cluster credit, not lifted code; their roles are listed in the PR body. Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: Manuel S. <3703103+Sakujakira@users.noreply.github.com> Co-authored-by: Eko Trisnanto <4469382+etrisnanto@users.noreply.github.com> Co-authored-by: aglowinthefield <146008217+aglowinthefield@users.noreply.github.com> Co-authored-by: GabboAlarcon <40816412+GabboAlarcon@users.noreply.github.com> (cherry picked from commit 7c55715)
Summary
Restores the activated dependency
site-packagesdirectory onto the external cron worker'sPYTHONPATHwhen spawned under a self-managed install.Root cause
Under a self-managed install, the gateway runs on the store Python and activates project dependencies by placing the selected generation's
site-packagesonsys.path. When spawning an external cron worker viasys.executable -m cron.scheduler, the subprocess sanitizer strips Hermes-owned pythonpaths, andpin_hermes_tree_on_pythonpathpreviously only restoredrepo_root. Since the store Python's own site-packages contains no third-party libraries, the worker crashed withModuleNotFoundError(such asNo module named 'ruamel') before publishing its ownership acknowledgement.Fix
cron/scheduler_worker_env.py, implemented_activated_site_packages()which scanssys.pathfor an activatedsite-packagesdirectory residing inside a real virtual environment (pyvenv.cfgancestor check) while excluding the interpreter's ownpurelib.pin_hermes_tree_on_pythonpath(), append the activatedsite-packagesnext torepo_root.tests/cron/test_restart_safe_worker.py, addedtest_pin_hermes_tree_restores_activated_dependency_site_packagesand convertedtest_launch_external_worker_pin_extends_the_sanitized_env_not_os_environto relationship assertions.Test plan
python -m py_compile cron/scheduler_worker_env.py tests/cron/test_restart_safe_worker.py(0 errors).test_pin_hermes_tree_restores_activated_dependency_site_packages._activated_site_packagesto returnNone, verifying expected failure (1 failed), then restored to clean pass (3/3 passed).Closes #122222