curl|bash installers abort on asdf/pyenv python3 shims that pass command -v but cannot run
A curl -fsSL https://example.com/setup.sh | bash installer uses set -euo pipefail and guards optional JSON/TOML merging with if command -v python3 >/dev/null 2>&1; then python3 - ... <<'EOF' ... else <portable fallback>; fi. On a user's machine with asdf installed, the script died partway (right after printing 'Writing MCP config...') with:
No preset version installed for command python3
Please install a version by running one of the following:
asdf install python 3.12.9
or add one of the following versions in your config file at ~/.tool-versions
python 3.12.9No config files were written, and the 'python3 not found' fallback branch never ran even though the user effectively had no usable Python. The guard looked correct: command -v python3 is the standard portable way to check for a binary.
Root cause: version-manager shims (asdf, and pyenv the same way) are real executables in a directory on PATH (~/.asdf/shims/python3, ~/.pyenv/shims/python3). command -v python3 therefore succeeds whenever the plugin is installed, regardless of whether a version is selected for the current directory. Only at execution does the shim discover no version is set, print the advice above to stderr, and exit non-zero. Under set -e that non-zero exit inside the then branch aborts the whole script.
Fix: check that the interpreter runs, not that it exists, and resolve once:
PY=""
PY_RESOLVED=false
find_python() {
if ! $PY_RESOLVED; then
PY_RESOLVED=true
local candidate
for candidate in python3 python; do
if command -v "$candidate" >/dev/null 2>&1 \
&& "$candidate" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 6) else 1)' >/dev/null 2>&1; then
PY="$candidate"
break
fi
done
fi
[ -n "$PY" ]
}
if find_python; then "$PY" - "$target" <<'EOF'
...
EOF
else
# portable fallback
fi- The probe runs inside an
ifcondition, soset -edoes not fire on a failing shim. >/dev/null 2>&1keeps the shim's advice text out of installer output.- The version floor also rejects a Python 2
python.
Regression test (bats): put fake shims first on PATH that mimic the failure: printf '#!/bin/sh\necho "No preset version installed for command python3" >&2\nexit 126\n' > shims/python3 (same for python), then assert the function exits 0, takes the fallback branch, and output lacks No preset version. Tests that simulated 'no python' by overriding command() for -v python3 should stub the resolver instead, or they silently fall through to python on hosts that have one.
User-side workaround: asdf set -u python <version> (asdf 0.16+) or pyenv global <version> so the shim resolves.