Skip to content

fix(gateway,cron): keep the pre-PM venv off sys.path on PM-managed Windows installs - #123996

Merged
kshitijk4poor merged 8 commits into
NousResearch:mainfrom
kshitijk4poor:fix/p1-0926-c2-committed-venv-imports
Sep 26, 2026
Merged

kshitijk4poor merged 8 commits into
NousResearch:mainfrom
kshitijk4poor:fix/p1-0926-c2-committed-venv-imports

Conversation

@kshitijk4poor

Copy link
Copy Markdown

On a PM-managed Windows install, the gateway and cron scripts no longer put the stale pre-PM in-tree venv on sys.path. They use the committed dependency generation, so hosted_room_worker stops crashing with No module named 'pydantic_core._pydantic_core'. Fixes #122183. This salvages #123149 by @robbyslmt (gateway side) and #123668 by @Halldrix (cron side).

Why

On a PM install, hermes_bootstrap has already activated the process onto the committed generation. Two late paths then added the legacy venv/Lib/site-packages on top of it, and that venv was built for a different Python ABI:

  • Gateway: _ensure_windows_gateway_venv_imports (gateway/run.py) always prepended the in-tree venv.
  • Cron: _windows_cron_python_invocation (cron/scheduler_script.py) overlaid selected_venv(repo). When nothing is committed, that falls back to base_venv, which is the same in-tree venv.

Changes

Validation

probe main this PR
gateway, PM install with committed gen legacy venv injected committed gen only
gateway, corrupt facts.json legacy venv injected RuntimeError (fail-closed)
gateway, non-PM install (nothing committed) overlay overlay (unchanged)
cron, committed gen in-tree venv on path committed gen
cron, nothing committed in-tree venv handed venv's own interpreter + packages

Tests:

  • Two invariant tests: tests/gateway/test_windows_venv_import_guard.py and tests/cron/test_cron_windows_venv_abi.py. Both carry pytest.mark.platforms("windows"), so scripts/ci/list_os_marked_tests.py windows selects both for the native Windows lanes.
  • macOS check: they skip on macOS. They were run through a fake-win32 harness: both pass on this branch, and both fail with main's gateway/run.py and cron/scheduler_script.py.
  • Mutation: with the gateway early return removed, the gateway test fails.
  • Scoped suites: failures are identical to main. The shared 3.12 venv can't run uv sync for the 3.14 pin, and TestRunJob* fails the same way on main.
  • Not tested yet: no run on a real Windows host. Only this PR's Windows CI lane will exercise it.

Not in this PR

Credit

Halldrix and others added 8 commits September 26, 2026 19:34
…usResearch#122183)

_windows_cron_python_invocation selected the dependency tree with
selected_venv, which falls back to base_venv and answers the leftover
pre-PM <root>/venv when no generation is recorded. That tree belongs to
whichever interpreter created it, so on a PM-managed install the managed
store Python 3.14 got a cp311 site-packages on PYTHONPATH and every cron
script died with "No module named 'pydantic_core._pydantic_core'" -- the
cron sibling of the gateway crash in NousResearch#122183, which janviernine flagged
there and left out of scope.

committed_venv never answers with the in-tree venv. With nothing committed
this install provisioned no tree, so overlay none and let the child keep
the interpreter it was handed rather than borrowing a foreign ABI.

Every other selected_venv caller (tools/environments/local_pythonpath,
hermes_cli/_early_recovery, pm/extras, pm/environments_adopt) already gates
on runtime_facts_path().is_file(); this was the only unguarded one.

(cherry picked from commit dd2b718)
…itted (NousResearch#122183)

The no-generation arm returned the bare store Python with a repo-only
overlay, which is worse than the main behavior it replaced: a store
Python with nothing committed is refused by _require_own_dependencies
with "hermes pm repair", and a cron script has no hermes_bootstrap to
refuse cleanly, so it runs and dies on its first third-party import. It
also stripped the overlay's site-packages entry, so
_windows_cron_bootstrap_argv warned on every spawn.

Fall through to the uv overlay instead: the handed venv keeps its own
base interpreter and its own packages, which is what worked on main.
The committed-generation arm is unchanged and still returns the store
Python with that generation's site-packages.

Also corrects the comment: site_packages is a pure path join on Windows
(pm/environments.py:250-255) and never raises there; the raise is the
pyvenv.cfg probe inside _recorded_venv:192, already covered here, and
dependency_site() is outside this try. (NousResearch#123668 review)

(cherry picked from commit b3760ef)
…#122183)

platforms("windows") instead of a bare skipif: list_os_marked_tests.py
gates the Windows lane on that marker, so the skipif version ran on no
CI lane at all -- the antipattern root AGENTS.md calls out.

Four tests collapse to the two invariants that actually matter: the
committed generation wins, and no-generation/unreadable-record both
fall through to the handed venv. The legacy non-managed test is gone --
it passes on main by design, and the fall-through test's positive
control now covers that path inside the managed arm.

(cherry picked from commit 30c649f)
…h#122183)

The salvage drops the try/except wrapper around committed_venv (a corrupt
facts.json raises as it did on main), so the "unreadable record falls
through" arm no longer describes the code. Keep the one invariant that
pins the bug: a committed generation, never the stale in-tree venv, is
what the cron child gets. Stack budget is two invariant tests.

Co-authored-by: Halldrix <halldrix@users.noreply.github.com>
…ateway (NousResearch#122183)

_ensure_windows_gateway_venv_imports prepended VIRTUAL_ENV or <root>/venv
unconditionally. On a PM install hermes_bootstrap has already activated
the store Python onto the committed generation, so that late overlay put
a foreign-ABI tree (cp311 pydantic_core under 3.14) first on sys.path and
the gateway died on import.

- committed generation present -> return: bootstrap already made the
  one activation decision; no second, late injection.
- committed_venv errors propagate (a corrupt facts.json is an error, not
  permission to load the legacy venv).
- nothing committed -> only overlay a candidate that is the running
  interpreter's own prefix, instead of parsing pyvenv.cfg versions (uv
  writes version_info, which a version-only parser misses).

Co-authored-by: Robby Slamet <robbyslmt@users.noreply.github.com>
…talls (NousResearch#122183)

The `Path(sys.prefix).resolve() != resolved_venv` guard skipped exactly the
case _ensure_windows_gateway_venv_imports exists for (264ac72): a gateway
restarted under uv's base pythonw.exe that still needs venv/Lib/site-packages
(MCP SDK). When sys.prefix IS the venv its site-packages is already on
sys.path, so the guard turned the function into a no-op on non-PM installs.

It is not needed for NousResearch#122183: the committed_venv early return alone keeps a
PM install off the leftover pre-PM venv (and a corrupt facts.json raising
there is fail-closed, matching hermes_bootstrap). Mutation-checked with a
fake-win32 harness: removing that early return turns
test_committed_generation_blocks_the_legacy_venv_overlay red.
…earch#122183)

The 19-line block narrated review history and still carried the base claim
"rather than interpreting relocated pyvenv.cfg", which is now wrong: with no
generation committed the code falls through to the handed venv's pyvenv.cfg.
Four lines state the rule. Comment-only.
…nv is patched (NousResearch#122183)

Neither kept test reaches a pyvenv.cfg read: the committed-generation path
returns before the cron fall-through, and the gateway overlay never parses
it. The `version=` arg, base/home dirs and the legacy cfg content were setup
nothing consumed. The cron test's selected_venv patch looks inert on head but
is what makes it red on base, so it now says so. Both tests still pass on head
and fail with base prod files (fake-win32 harness).
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) September 26, 2026 14:14
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/gateway Gateway runner, session dispatch, delivery comp/cron Cron scheduler and job management platform/windows area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Sep 26, 2026
@kshitijk4poor
kshitijk4poor merged commit 86f449b into NousResearch:main Sep 26, 2026
49 checks passed
@alt-glitch alt-glitch added os/windows Native Windows behaviour or breakage: PowerShell install, Windows paths, cmd.exe, WSL-vs-native diff and removed platform/windows labels Oct 17, 2026
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/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery os/windows Native Windows behaviour or breakage: PowerShell install, Windows paths, cmd.exe, WSL-vs-native diff P1 High — major feature broken, no workaround sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Windows gateway on the PM runtime prepends the pre-PM venv and crashes hosted_room_worker (No module named 'pydantic_core._pydantic_core')

3 participants