From 2cea81a5bad8d21ad97f6315b2aeddc60b77992e Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sun, 30 Aug 2026 03:53:57 +0300 Subject: [PATCH] fix(install): survive pip-less uv-created Hermes venvs; stop hiding errors Found installing on a fresh Debian 13 machine (Hermes installed via its own uv-based install.sh): - Root cause: 'uv venv' does not seed pip into the Hermes runtime venv, so '$PYTHON -m pip install' died with 'No module named pip' and the installer's suggested manual pip command would have failed the same way. Dependency installation now chains: pip -> ensurepip-bootstrapped pip -> 'uv pip install --python' (uv probed on PATH and at $HERMES_HOME/bin/uv, covering non-login SSH shells with minimal PATH). - No more silent failures: every fallible step (deps, model preload, hermes config set, plugin validation) captured stderr behind 2>/dev/null, making the remote failure undebuggable. All of them now capture combined output into DIAG_DIR (cleaned via EXIT trap) and print a show_log_tail excerpt on failure; failure guidance names commands that actually work on a pip-less host (uv, ensurepip). - Docs: Documentation/index.org step 2 describes the install chain; AGENTS.org gains the uv-venv-has-no-pip and no-silent-2>/dev/null pitfalls and the tooling note describes the new chain. Verified with a stubbed-environment ad-hoc harness (20/20): pip-less uv venv + broken ensurepip + uv present (exact remote repro) installs via uv; pip-present venv uses pip; total failure prints captured diagnostics and working manual commands. Canonical suite green. --- AGENTS.org | 24 ++++++++-- Documentation/index.org | 5 ++- Install Retinue | 99 +++++++++++++++++++++++++++++++++++------ 3 files changed, 110 insertions(+), 18 deletions(-) diff --git a/AGENTS.org b/AGENTS.org index 2307863..3d286a1 100644 --- a/AGENTS.org +++ b/AGENTS.org @@ -203,10 +203,11 @@ Never commit the generated =*.html= — they are gitignored. - *Python on Debian 13 is PEP 668.* System pip is blocked. The installer deliberately installs deps into the Python that runs Hermes (=PYTHON= env override, =$HERMES_HOME/hermes-agent/venv/bin/python3=, - or the =hermes= launcher shebang), and falls back to - =--break-system-packages= only inside that interpreter. The standalone - tests use the project =.venv=. Do not pip-install into the system - Python from this project. + or the =hermes= launcher shebang). That interpreter is a uv-created venv + without pip (see pitfall below), so dependency installation tries pip, + then =ensurepip=-bootstrapped pip, then =uv= from =PATH= or + =$HERMES_HOME/bin/uv=. Do not pip-install into the system Python from + this project. - *Filenames with spaces.* Everything under =Tools/= contains spaces — quote paths in shell commands. - *The =Tools/Open with IntelliJ IDEA= helper* opens this tree as an @@ -267,6 +268,21 @@ Never commit the generated =*.html= — they are gitignored. Hermes at =~/.hermes/bin/uv=), then falls back to the Hermes runtime venv. All =Tools/= scripts source it — never open-code venv handling in a Tools script. +- *Pitfall: the Hermes runtime venv has no pip.* Hermes's own installer + creates =~/.hermes/hermes-agent/venv= with =uv venv=, which does not seed + pip — =venv/bin/python3 -m pip= dies with =No module named pip= (found + 2026-08-30 installing Retinue on a fresh Debian 13 machine: both pip + attempts failed, and the suggested manual =pip install= fix would have + failed the same way). The installer now chains pip → ensurepip → uv + (=$HERMES_HOME/bin/uv=, probed explicitly because non-login shells have a + minimal PATH) and prints captured attempt output on failure instead of + hiding it behind =2>/dev/null=. Keep every one of those fallbacks. +- *Pitfall: silent failure via =2>/dev/null=.* The 2026-08-30 install + failure was undebuggable because the installer discarded stderr. Every + fallible step (deps, model preload, =hermes config set=, plugin + validation) now captures combined output into a temp dir (=DIAG_DIR=, + cleaned by an EXIT trap) and prints a =show_log_tail= excerpt on + failure. Never reintroduce bare =2>/dev/null= on a step that can fail. - *Pitfall: gateway restart on the remote.* =systemctl --user restart hermes-gateway.service= can leave the unit in =activating= when an old gateway process (started with =gateway run --replace= outside systemd) diff --git a/Documentation/index.org b/Documentation/index.org index 38dba61..c1c1ace 100644 --- a/Documentation/index.org +++ b/Documentation/index.org @@ -69,7 +69,10 @@ The installer performs four steps: 1. Detects the active Hermes home from the =HERMES_HOME= environment variable or falls back to =$HOME/.hermes=. 2. Resolves the Python interpreter that Hermes uses and installs =sqlite-vec= - and =model2vec= there if they are missing. + and =model2vec= there if they are missing — via pip when present, via + =ensurepip=-bootstrapped pip otherwise, and finally via =uv= (Hermes + bundles one at =~/.hermes/bin/uv=), which also covers uv-created Hermes + venvs that ship without pip. 3. Copies the Retinue source into =$HERMES_HOME/plugins/retinue/=, preserving any existing runtime data. 4. Pre-downloads the embedding model diff --git a/Install Retinue b/Install Retinue index fb17d83..c0db46a 100755 --- a/Install Retinue +++ b/Install Retinue @@ -12,6 +12,19 @@ set -euo pipefail +# Diagnostics: every step that can fail captures its combined output here and +# prints it on failure. Never hide stderr behind 2>/dev/null — a silent +# failure on an unfamiliar host is undebuggable (found 2026-08-30: a pip-less +# Hermes venv failed the install with zero visible reason). +DIAG_DIR="$(mktemp -d)" +trap 'rm -rf "$DIAG_DIR"' EXIT + +# Print the last lines of a captured log, tolerating tqdm-style \r progress +# spam (model downloads) that would otherwise arrive as one giant line. +show_log_tail() { # show_log_tail + tr '\r' '\n' <"$1" | tail -n "${2:-15}" | sed 's/^/ /' +} + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_DIR="$SCRIPT_DIR" @@ -135,6 +148,14 @@ done # Ensure Python dependencies are available in the target interpreter. # Hermes itself runs with this Python, so the deps must be importable there. +# +# The Hermes runtime venv is created by uv, which does NOT seed pip into it — +# "$PYTHON -m pip" fails there with "No module named pip". On bare Debian 13 +# hosts ensurepip is also missing (no python3.X-venv apt package), so the +# reliable path is the uv binary Hermes itself installed at +# $HERMES_HOME/bin/uv. Try every available installer in order and capture +# each attempt's output so a total failure can show real diagnostics +# instead of failing silently. ensure_python_deps() { local missing="" if ! "$PYTHON" -c "import sqlite_vec" 2>/dev/null; then @@ -148,21 +169,63 @@ ensure_python_deps() { fi echo "Missing Python dependencies:$missing" - echo "Installing with $PYTHON -m pip ..." - if "$PYTHON" -m pip install $missing 2>/dev/null; then - echo "Dependencies installed successfully." - return 0 + local logdir attempt=0 + logdir="$(mktemp -d)" + + try_install() { # try_install