install: warn when a stale binary shadows what we just installed

A binary earlier on PATH permanently masks the one the installer writes, and
the failure is SILENT in the worst way: the install reports success, the user
runs the old copy forever, and re-running the installer never fixes it because
it writes to a DIFFERENT directory.

Found live: ~/.local/bin/hanzo held a retired Go build (it reports version
'dev') while the canonical Rust CLI installs to ~/.cargo/bin. uv also installs
tools into ~/.local/bin, so a Python `hanzo` shadows the same way. Someone in
that state debugs a CLI they are not running.

check_shadowing compares `command -v <bin>` against where we actually put it,
for hanzo/mcp/dev/hanzod, and prints the exact mv to fix it. It does NOT delete
anything — the user's files are theirs — but staying quiet is what makes this
cost hours.

Verified all three cases with a harness against the real function:
  stale first on PATH  -> warns, naming both paths
  canonical first      -> silent (no false positive)
  canonical not present-> silent (nothing to shadow)
(My first harness run passed for the wrong reason — I had created `cargo/bin`
instead of `.cargo/bin`, so the check correctly found nothing. Fixed the
harness, then it caught the case.)
This commit is contained in:
2026-07-26 00:47:52 -07:00
committed by z
parent d42428b39e
commit 52207b4cfa
+32
View File
@@ -426,6 +426,36 @@ summary() {
fi
}
check_shadowing() {
# A stale binary earlier on PATH permanently masks what we just installed,
# and the failure is silent: the install reports success, `hanzo --version`
# prints whatever the old copy says, and re-running the installer never
# fixes it because it writes to a DIFFERENT directory.
#
# Real case: ~/.local/bin/hanzo held a retired Go build while the canonical
# Rust CLI installs to ~/.cargo/bin. uv also installs tools into
# ~/.local/bin, so a Python `hanzo` shadows it the same way. We do NOT
# delete anything — the user's files are theirs — but staying quiet about it
# is how someone ends up debugging a CLI they are not running.
local resolved expected
for bin in hanzo mcp dev hanzod; do
has_cmd "$bin" || continue
resolved="$(command -v "$bin" 2>/dev/null)"
expected=""
[[ -x "$HOME/.cargo/bin/$bin" ]] && expected="$HOME/.cargo/bin/$bin"
[[ -z "$expected" && -x "$HANZO_DIR/$bin" ]] && expected="$HANZO_DIR/$bin"
[[ -z "$expected" ]] && continue
if [[ "$resolved" != "$expected" ]]; then
warn "$bin resolves to $resolved, shadowing $expected"
echo -e " ${DM}the copy on your PATH is not the one just installed.${N}"
echo -e " ${DM}remove or rename it, or put its directory later on PATH:${N}"
echo " mv $resolved $resolved.old"
fi
done
}
finish() {
echo ""
@@ -450,6 +480,8 @@ finish() {
echo ""
fi
check_shadowing
if [[ ":$PATH:" != *":$HANZO_DIR:"* ]] && [[ ${#INSTALLED[@]} -gt 0 ]]; then
echo -e " ${Y}add to path:${N}"
echo " export PATH=\"$HANZO_DIR:\$PATH\""