diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 991af42e2f1..916d33bba1a 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -7724,8 +7724,9 @@ def _ensure_uv_for_termux(pip_cmd: list[str]) -> str | None: The normal path (``ensure_uv()`` in managed_uv) installs the managed standalone uv into ``$HERMES_HOME/bin/uv``, but on Termux the official - installer may not work (glibc vs bionic). Fall back to ``pip install uv`` - which gets a Termux-compatible binary. + installer may not work (glibc vs bionic). Prefer a uv already on PATH + (e.g. ``pkg install uv``); only if there is none do we fall back to a + wheel-only ``pip install uv`` so we never source-build the Rust crate. """ from hermes_cli.managed_uv import resolve_uv @@ -7734,6 +7735,12 @@ def _ensure_uv_for_termux(pip_cmd: list[str]) -> str | None: return existing if not _is_termux_env(): return None + # A Termux-packaged uv lands on PATH but not in the managed bin dir, so + # resolve_uv() misses it. Use it before pip, which has no Android wheel and + # would otherwise build uv from source on a low-memory device. + system_uv = shutil.which("uv") + if system_uv: + return system_uv try: print(" → Termux detected: trying to install uv for faster dependency updates...") result = subprocess.run( diff --git a/tests/hermes_cli/test_cmd_update.py b/tests/hermes_cli/test_cmd_update.py index 631e226640f..56671aa7ecc 100644 --- a/tests/hermes_cli/test_cmd_update.py +++ b/tests/hermes_cli/test_cmd_update.py @@ -138,6 +138,23 @@ class TestCmdUpdateTermuxUvBootstrap: assert mock_run.call_args.kwargs["cwd"] == PROJECT_ROOT assert mock_run.call_args.kwargs["check"] is False + @patch("subprocess.run") + def test_termux_reuses_existing_path_uv_without_pip(self, mock_run, monkeypatch): + """A uv already on PATH (e.g. ``pkg install uv``) is reused before pip runs.""" + from hermes_cli import main as hm + + pkg_uv = "/data/data/com.termux/files/usr/bin/uv" + monkeypatch.setattr(hm, "_is_termux_env", lambda env=None: True) + # Production resolve_uv only checks $HERMES_HOME/bin/uv; model an empty + # managed dir so the PATH probe is what surfaces the packaged uv. + monkeypatch.setattr("hermes_cli.managed_uv.resolve_uv", lambda: None) + monkeypatch.setattr("shutil.which", lambda name: pkg_uv if name == "uv" else None) + + uv_bin = hm._ensure_uv_for_termux(["/termux/python", "-m", "pip"]) + + assert uv_bin == pkg_uv + mock_run.assert_not_called() + class TestCmdUpdateBranchFallback: """cmd_update falls back to main when current branch has no remote counterpart."""