From d186186e1af74c3e4568e4775d55e0f24f5c2071 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 12 May 2026 12:11:16 -0700 Subject: [PATCH] fix(install): surface uv install + uv.lock sync errors instead of silently hanging (#24504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The c1eb2dcda tiered installer made two install paths look frozen on slow networks or broken environments because both swallowed the underlying tool's stderr. scripts/install.sh, setup-hermes.sh: curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null printed only '✗ Failed to install uv' on failure with no diagnostic. Common real causes (glibc mismatch on old distros, corp proxy / TLS interception, missing curl, ~/.local/bin not writable, disk full) were invisible. Also: piping curl into sh masks curl failures under set -e (no pipefail) — sh exits 0 on empty stdin, so a network error succeeded silently. Fix: download installer to a tempfile first, then run it. Capture curl + installer output to a log; on failure, indent and print it. scripts/install.sh hash-verified tier: uv sync --all-extras --locked 2>"$(mktemp)" silenced uv's progress output, making a fresh-venv install (~50 transitives including torch-class deps) look hung for 1-5 minutes — users see 'Trying tier: hash-verified (uv.lock) ...' and assume it's frozen. The mktemp substitution also wasn't saved to a variable, so the uv error on failure was unreachable. Fix: stream uv's stderr directly so users see live 'Resolved N / Prepared / Installed' progress. Print an upfront note that the first run takes 1-5 minutes. --- scripts/install.sh | 50 ++++++++++++++++++++++++++++++++++++++++++---- setup-hermes.sh | 29 ++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index f4fccea7d9e..c54f9ad9ae0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -366,7 +366,27 @@ install_uv() { # Install uv log_info "Installing uv (fast Python package manager)..." - if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then + # Capture installer output so a failure shows the user WHY (network, + # glibc mismatch on old distros, missing curl, ~/.local/bin not + # writable, disk full, corp proxy / TLS interception, etc.) instead + # of the previous "✗ Failed to install uv" with zero diagnostic. + # + # Two-stage: download the installer, then run it. Piping + # `curl | sh` masks curl failures (sh exits 0 on empty stdin) + # and conflates network errors with installer errors. + local _uv_install_log _uv_installer + _uv_install_log="$(mktemp 2>/dev/null || echo "/tmp/hermes-uv-install.$$.log")" + _uv_installer="$(mktemp 2>/dev/null || echo "/tmp/hermes-uv-installer.$$.sh")" + if ! curl -LsSf https://astral.sh/uv/install.sh -o "$_uv_installer" 2>"$_uv_install_log"; then + log_error "Failed to download uv installer from https://astral.sh/uv/install.sh" + log_info "curl output:" + sed 's/^/ /' "$_uv_install_log" >&2 + log_info "Install manually: https://docs.astral.sh/uv/getting-started/installation/" + rm -f "$_uv_install_log" "$_uv_installer" + exit 1 + fi + if sh "$_uv_installer" >>"$_uv_install_log" 2>&1; then + rm -f "$_uv_installer" # uv installs to ~/.local/bin by default if [ -x "$HOME/.local/bin/uv" ]; then UV_CMD="$HOME/.local/bin/uv" @@ -375,15 +395,22 @@ install_uv() { elif command -v uv &> /dev/null; then UV_CMD="uv" else - log_error "uv installed but not found on PATH" + log_error "uv installer reported success but binary not found on PATH" + log_info "Installer output:" + sed 's/^/ /' "$_uv_install_log" >&2 log_info "Try adding ~/.local/bin to your PATH and re-running" + rm -f "$_uv_install_log" exit 1 fi + rm -f "$_uv_install_log" UV_VERSION=$($UV_CMD --version 2>/dev/null) log_success "uv installed ($UV_VERSION)" else log_error "Failed to install uv" + log_info "Installer output:" + sed 's/^/ /' "$_uv_install_log" >&2 log_info "Install manually: https://docs.astral.sh/uv/getting-started/installation/" + rm -f "$_uv_install_log" "$_uv_installer" exit 1 fi } @@ -1073,12 +1100,27 @@ install_deps() { # extras spec, NOT because they're equivalent in posture. if [ -f "uv.lock" ]; then log_info "Trying tier: hash-verified (uv.lock) ..." - if UV_PROJECT_ENVIRONMENT="$INSTALL_DIR/venv" $UV_CMD sync --all-extras --locked 2>"$(mktemp)"; then + log_info "(this resolves + downloads ~50 packages — first run on a fresh" + log_info " venv can take 1-5 minutes; uv prints progress below)" + # Stream uv's progress directly to the user instead of swallowing + # it with `2>"$(mktemp)"`. Two reasons: + # 1. `--all-extras --locked` against a fresh venv has to pull + # every transitive (torch-class deps included) — silencing + # stderr makes the install look frozen for minutes on slow + # networks. Users see "Trying tier: hash-verified ..." and + # assume it's hung. + # 2. The previous `2>"$(mktemp)"` substituted the path at + # command-build time but never saved it, so on failure the + # uv error message was unreachable — the user just got the + # generic "lockfile may be stale" warning. + # uv's own progress UI handles TTY detection and downgrades + # gracefully when stdout/stderr aren't terminals. + if UV_PROJECT_ENVIRONMENT="$INSTALL_DIR/venv" $UV_CMD sync --all-extras --locked; then log_success "Main package installed (hash-verified via uv.lock)" log_success "All dependencies installed" return 0 fi - log_warn "uv.lock sync failed (lockfile may be stale), falling back to PyPI resolve..." + log_warn "uv.lock sync failed (see uv output above), falling back to PyPI resolve..." else log_info "uv.lock not found — falling back to PyPI resolve (no hash verification)" fi diff --git a/setup-hermes.sh b/setup-hermes.sh index 9690d6a23a6..0b214b0633c 100755 --- a/setup-hermes.sh +++ b/setup-hermes.sh @@ -82,7 +82,22 @@ else echo -e "${GREEN}✓${NC} uv found ($UV_VERSION)" else echo -e "${CYAN}→${NC} Installing uv..." - if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then + # Capture installer output so a failure shows the user WHY + # (network, glibc mismatch on old distros, missing curl, disk + # full, etc.) instead of "✗ Failed to install uv" with zero + # diagnostic. Two-stage to avoid `curl | sh` masking curl + # failures (sh exits 0 on empty stdin under no pipefail). + _uv_log="$(mktemp 2>/dev/null || echo "/tmp/hermes-uv-install.$$.log")" + _uv_installer="$(mktemp 2>/dev/null || echo "/tmp/hermes-uv-installer.$$.sh")" + if ! curl -LsSf https://astral.sh/uv/install.sh -o "$_uv_installer" 2>"$_uv_log"; then + echo -e "${RED}✗${NC} Failed to download uv installer." + sed 's/^/ /' "$_uv_log" >&2 + echo -e "${CYAN}→${NC} Install manually: https://docs.astral.sh/uv/" + rm -f "$_uv_log" "$_uv_installer" + exit 1 + fi + if sh "$_uv_installer" >>"$_uv_log" 2>&1; then + rm -f "$_uv_installer" if [ -x "$HOME/.local/bin/uv" ]; then UV_CMD="$HOME/.local/bin/uv" elif [ -x "$HOME/.cargo/bin/uv" ]; then @@ -90,14 +105,22 @@ else fi if [ -n "$UV_CMD" ]; then + rm -f "$_uv_log" UV_VERSION=$($UV_CMD --version 2>/dev/null) echo -e "${GREEN}✓${NC} uv installed ($UV_VERSION)" else - echo -e "${RED}✗${NC} uv installed but not found. Add ~/.local/bin to PATH and retry." + echo -e "${RED}✗${NC} uv installer reported success but binary not found. Add ~/.local/bin to PATH and retry." + echo -e "${CYAN}→${NC} Installer output:" + sed 's/^/ /' "$_uv_log" >&2 + rm -f "$_uv_log" exit 1 fi else - echo -e "${RED}✗${NC} Failed to install uv. Visit https://docs.astral.sh/uv/" + echo -e "${RED}✗${NC} Failed to install uv." + echo -e "${CYAN}→${NC} Installer output:" + sed 's/^/ /' "$_uv_log" >&2 + echo -e "${CYAN}→${NC} Install manually: https://docs.astral.sh/uv/" + rm -f "$_uv_log" "$_uv_installer" exit 1 fi fi