fix(lazy-deps): skip the install ladder on package-manager installs

Salvage of #48637 (Fixes #48628). On a NixOS-style install the venv's
site-packages lives in the read-only store, so ensure()'s
uv -> pip -> ensurepip ladder spends ~15s bootstrapping ensurepip only
to fail against a target it can never write. Fail fast with an
actionable message pointing at the system package manager.

Retargeted onto current main (the PR's base predates the durable-target
subsystem by ~8.1K commits) with two corrections to the original:

- Gate on _lazy_install_target() is None. The container deployment sets
  HERMES_MANAGED=true AND HERMES_LAZY_INSTALL_TARGET (a writable
  volume); the original guard would have blocked installs that path
  legitimately satisfies, breaking the NixOS-container mode.
- Reason string starts with 'unsupported ' because
  refresh_active_features classifies FeatureUnavailable by that prefix;
  the original wording made 'hermes update' report a hard failure
  instead of a skip.

Placed after _unsupported_feature_reason so a platform-specific reason
(more actionable) wins, and so ensure() agrees with
refresh_active_features, which pre-checks that same function.
This commit is contained in:
liuhao1024
2026-08-03 14:03:40 +05:30
committed by kshitij
parent 2ba064bc54
commit f07f47fe7d
2 changed files with 149 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
"""Managed-install guard in :func:`tools.lazy_deps.ensure` (#48628).
A package-manager install (NixOS, and anything else shipping Hermes from a
read-only store) cannot receive lazy pip installs: the venv's site-packages
lives in the store, so the uv -> pip -> ensurepip ladder burns ~15s
bootstrapping ensurepip only to fail. ``ensure()`` must fail fast instead.
"""
import pytest
from tools import lazy_deps
from tools.lazy_deps import FeatureUnavailable
FEATURE = "provider.anthropic"
@pytest.fixture(autouse=True)
def _missing_and_installable(monkeypatch):
"""Reach the guard: deps missing, installs allowed, no durable target.
``_allow_lazy_installs`` is patched explicitly so the suite does not
depend on the host's ~/.hermes/config.yaml (a local
``allow_lazy_installs: false`` otherwise short-circuits with a different
rejection reason).
"""
monkeypatch.setattr(lazy_deps, "feature_missing", lambda _f: ("some-pkg==1.0",))
monkeypatch.setattr(lazy_deps, "_allow_lazy_installs", lambda: True)
monkeypatch.setattr(lazy_deps, "_lazy_install_target", lambda: None)
def _no_installer(monkeypatch):
"""Fail loudly if the guard lets execution reach the install ladder."""
def _boom(*_a, **_kw):
raise AssertionError("guard let execution reach the install ladder")
monkeypatch.setattr(lazy_deps.subprocess, "run", _boom)
def test_nixos_install_fails_fast_without_touching_the_installer(monkeypatch):
monkeypatch.setattr("hermes_cli.config.get_managed_system", lambda: "nixos")
_no_installer(monkeypatch)
with pytest.raises(FeatureUnavailable) as excinfo:
lazy_deps.ensure(FEATURE, prompt=False)
assert "nixos" in excinfo.value.reason
# refresh_active_features classifies by this prefix — anything else is
# reported to the user as a hard failure instead of a skip.
assert excinfo.value.reason.startswith("unsupported ")
def test_reason_is_classified_as_skipped_not_failed(monkeypatch):
"""The wording contract with refresh_active_features, pinned directly."""
monkeypatch.setattr("hermes_cli.config.get_managed_system", lambda: "nixos")
with pytest.raises(FeatureUnavailable) as excinfo:
lazy_deps.ensure(FEATURE, prompt=False)
assert excinfo.value.reason.startswith("unsupported "), (
"refresh_active_features would report this as failed: rather than skipped:"
)
def test_unmanaged_install_is_not_blocked_by_the_guard(monkeypatch):
"""On a normal pip install the guard must be transparent."""
monkeypatch.setattr("hermes_cli.config.get_managed_system", lambda: None)
with pytest.raises(FeatureUnavailable) as excinfo:
lazy_deps.ensure(FEATURE, prompt=False)
# Whatever stops the install here, it must NOT be the managed guard.
assert "managed installs" not in excinfo.value.reason
def test_durable_install_target_overrides_the_guard(monkeypatch, tmp_path):
"""The container deployment sets HERMES_MANAGED *and* a writable target.
Dockerfile sets HERMES_LAZY_INSTALL_TARGET and the NixOS container module
passes HERMES_MANAGED=true; blocking there would break that deployment.
"""
monkeypatch.setattr("hermes_cli.config.get_managed_system", lambda: "nixos")
monkeypatch.setattr(lazy_deps, "_lazy_install_target", lambda: tmp_path)
with pytest.raises(FeatureUnavailable) as excinfo:
lazy_deps.ensure(FEATURE, prompt=False)
assert "nixos" not in excinfo.value.reason.lower(), (
"durable-target installs must not be blocked by the managed guard"
)
def test_platform_unsupported_takes_precedence(monkeypatch):
"""A platform-specific reason is more actionable than 'managed install'.
Also required for consistency: refresh_active_features pre-checks
_unsupported_feature_reason before calling ensure().
"""
monkeypatch.setattr("hermes_cli.config.get_managed_system", lambda: "nixos")
monkeypatch.setattr(
lazy_deps, "_unsupported_feature_reason", lambda _f: "unsupported on win32"
)
with pytest.raises(FeatureUnavailable) as excinfo:
lazy_deps.ensure(FEATURE, prompt=False)
assert excinfo.value.reason == "unsupported on win32"
def test_unreadable_config_fails_open(monkeypatch):
"""A broken config must not block installs on a normal pip install."""
def _raise():
raise RuntimeError("config unreadable")
monkeypatch.setattr("hermes_cli.config.get_managed_system", _raise)
with pytest.raises(FeatureUnavailable) as excinfo:
lazy_deps.ensure(FEATURE, prompt=False)
assert "managed" not in excinfo.value.reason.lower()

View File

@@ -847,6 +847,35 @@ def ensure(feature: str, *, prompt: bool = True) -> None:
if unsupported:
raise FeatureUnavailable(feature, missing, unsupported)
# Package-manager installs (NixOS, and any other distro that ships Hermes
# from a read-only store) cannot receive lazy pip installs: the venv's
# site-packages lives in the store, so the uv -> pip -> ensurepip ladder
# below burns ~15s bootstrapping ensurepip only to fail on a read-only
# target. Fail fast with an actionable message instead.
#
# Skipped when a durable install target is configured: the container
# deployment sets HERMES_MANAGED=true *and* HERMES_LAZY_INSTALL_TARGET
# (a writable volume), where lazy installs legitimately work.
#
# The reason string starts with "unsupported " on purpose:
# refresh_active_features classifies FeatureUnavailable by that prefix and
# reports anything else as a hard failure rather than a skip.
if _lazy_install_target() is None:
try:
from hermes_cli.config import get_managed_system
managed_by = get_managed_system()
except Exception:
managed_by = "" # config unreadable — proceed with the install
if managed_by:
raise FeatureUnavailable(
feature, missing,
f"unsupported on {managed_by}-managed installs: this build's "
f"packages come from {managed_by}, so Hermes cannot install "
f"them at runtime. Add the dependencies for {feature!r} via "
f"{managed_by} (or run a pip/uv install of Hermes instead)."
)
# Validate every spec against the allowlist + safety regex. Belt and
# braces — the keys-in-LAZY_DEPS check above already constrains this.
for spec in missing: