Fix installer Node runtime mismatch (#68)

* Fix installer Node runtime mismatch

Ensure install.sh uses Node >=22 for npm install steps even when
nvm Node 20 is first on PATH. Add a compatibility shim so the
installed openclaw command still runs via a supported Node runtime
in mixed-version shells.

Also fixes shellcheck warning in shim log output.

* fix: keep installer compat shim in user-local bin (#68) (thanks @rolandkakonyi)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Roland Kákonyi 2026-03-07 20:41:25 +01:00 committed by GitHub
parent 5dcce94bc2
commit 8cee7c3288
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 231 additions and 0 deletions

View File

@ -8,6 +8,7 @@
- Blog: restore the missing Discord link in the VirusTotal partnership post footer (#96, thanks @gandli).
- Dependencies: bump `@lucide/astro` to `0.577.0` and sync `bun.lock` (#99, thanks @dependabot).
- CI: update Bun setup pin and move the install-smoke Node setup to the pinned `actions/setup-node` v6 SHA (#98, thanks @dependabot).
- Installer: recover from older PATH-bound Node runtimes after install, but keep the fallback `openclaw` shim in `~/.local/bin` instead of mutating version-manager bins (#68, thanks @rolandkakonyi).
## 2026-02-22
- Installer: make gum behavior fully automatic (interactive TTYs get gum, headless shells get plain status), and remove manual gum toggles.

View File

@ -959,6 +959,7 @@ NPM_LOGLEVEL="${OPENCLAW_NPM_LOGLEVEL:-error}"
NPM_SILENT_FLAG="--silent"
VERBOSE="${OPENCLAW_VERBOSE:-0}"
OPENCLAW_BIN=""
SELECTED_NODE_BIN=""
PNPM_CMD=()
HELP=0
@ -1301,6 +1302,163 @@ check_node() {
fi
}
node_major_from_binary() {
local node_bin="$1"
if [[ -z "$node_bin" || ! -x "$node_bin" ]]; then
return 1
fi
"$node_bin" -p 'process.versions.node.split(".")[0]' 2>/dev/null || true
}
node_is_supported_binary() {
local node_bin="$1"
local major=""
major="$(node_major_from_binary "$node_bin")"
if [[ ! "$major" =~ ^[0-9]+$ ]]; then
return 1
fi
[[ "$major" -ge 22 ]]
}
has_supported_node() {
local node_bin=""
node_bin="$(command -v node 2>/dev/null || true)"
if [[ -z "$node_bin" ]]; then
return 1
fi
node_is_supported_binary "$node_bin"
}
prepend_path_dir() {
local dir="${1%/}"
if [[ -z "$dir" || ! -d "$dir" ]]; then
return 1
fi
local current=":${PATH:-}:"
current="${current//:${dir}:/:}"
current="${current#:}"
current="${current%:}"
if [[ -n "$current" ]]; then
export PATH="${dir}:${current}"
else
export PATH="${dir}"
fi
hash -r 2>/dev/null || true
}
ensure_supported_node_on_path() {
if has_supported_node; then
SELECTED_NODE_BIN="$(command -v node 2>/dev/null || true)"
return 0
fi
local -a candidates=()
local candidate=""
while IFS= read -r candidate; do
[[ -n "$candidate" ]] && candidates+=("$candidate")
done < <(type -aP node 2>/dev/null || true)
candidates+=(
"/usr/bin/node"
"/usr/local/bin/node"
"/opt/homebrew/bin/node"
"/opt/homebrew/opt/node@22/bin/node"
"/usr/local/opt/node@22/bin/node"
)
local seen=":"
for candidate in "${candidates[@]}"; do
if [[ -z "$candidate" || ! -x "$candidate" ]]; then
continue
fi
case "$seen" in
*":$candidate:"*) continue ;;
esac
seen="${seen}${candidate}:"
if node_is_supported_binary "$candidate"; then
prepend_path_dir "$(dirname "$candidate")" || continue
SELECTED_NODE_BIN="$candidate"
ui_info "Using Node.js runtime at ${candidate}"
return 0
fi
done
return 1
}
original_path_node_bin() {
if [[ -z "${ORIGINAL_PATH:-}" ]]; then
return 1
fi
PATH="$ORIGINAL_PATH" command -v node 2>/dev/null || true
}
original_path_has_supported_node() {
local node_bin=""
node_bin="$(original_path_node_bin)"
if [[ -z "$node_bin" ]]; then
return 1
fi
node_is_supported_binary "$node_bin"
}
find_openclaw_entry_path() {
local npm_root=""
npm_root="$(npm root -g 2>/dev/null || true)"
if [[ -z "$npm_root" ]]; then
return 1
fi
local entry_js="${npm_root}/openclaw/dist/entry.js"
if [[ -f "$entry_js" ]]; then
echo "$entry_js"
return 0
fi
local entry_mjs="${npm_root}/openclaw/dist/entry.mjs"
if [[ -f "$entry_mjs" ]]; then
echo "$entry_mjs"
return 0
fi
return 1
}
install_openclaw_compat_shim() {
if [[ "$INSTALL_METHOD" != "npm" ]]; then
return 0
fi
if original_path_has_supported_node; then
return 0
fi
local node_bin="${SELECTED_NODE_BIN:-}"
if [[ -z "$node_bin" ]]; then
node_bin="$(command -v node 2>/dev/null || true)"
fi
if [[ -z "$node_bin" || ! -x "$node_bin" ]] || ! node_is_supported_binary "$node_bin"; then
return 1
fi
local entry_path=""
entry_path="$(find_openclaw_entry_path || true)"
if [[ -z "$entry_path" ]]; then
return 1
fi
local target_dir="$HOME/.local/bin"
ensure_user_local_bin_on_path
mkdir -p "$target_dir"
local shim_path="${target_dir}/openclaw"
cat > "$shim_path" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec "$node_bin" "$entry_path" "\$@"
EOF
chmod +x "$shim_path"
refresh_shell_command_cache
ui_warn "Configured openclaw shim at ${shim_path} for Node $("$node_bin" -v 2>/dev/null || echo '22+')"
return 0
}
# Install Node.js
install_node() {
if [[ "$OS" == "macos" ]]; then
@ -2147,6 +2305,14 @@ main() {
if ! check_node; then
install_node
fi
ensure_supported_node_on_path || true
if ! has_supported_node; then
ui_error "Node.js v22+ is required but could not be activated on PATH"
echo "Detected node: $(command -v node 2>/dev/null || echo '(not found)')"
echo "Current version: $(node -v 2>/dev/null || echo 'unknown')"
echo "Install Node.js 22+ manually: https://nodejs.org"
exit 1
fi
ui_stage "Installing OpenClaw"
@ -2183,6 +2349,7 @@ main() {
# Step 5: OpenClaw
install_openclaw
install_openclaw_compat_shim || true
fi
ui_stage "Finalizing setup"

View File

@ -510,4 +510,67 @@ echo "==> case: install_openclaw_from_git (deps step uses run_pnpm function)"
assert_eq "$deps_cmd" "run_pnpm" "install_openclaw_from_git dependencies command"
)
echo "==> case: install_openclaw_compat_shim (always uses user-local bin)"
(
root="${TMP_DIR}/case-openclaw-compat-shim"
home_dir="${root}/home"
selected_bin_dir="${root}/node22/bin"
original_bin_dir="${root}/nvm/bin"
pkg_dir="${root}/npm-root/openclaw/dist"
mkdir -p "${home_dir}" "${selected_bin_dir}" "${original_bin_dir}" "${pkg_dir}"
: > "${pkg_dir}/entry.js"
cat >"${selected_bin_dir}/node" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [[ "${1:-}" == "-p" ]]; then
echo "22"
exit 0
fi
if [[ "${1:-}" == "-v" ]]; then
echo "v22.12.0"
exit 0
fi
exit 0
EOF
chmod +x "${selected_bin_dir}/node"
cat >"${original_bin_dir}/node" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [[ "${1:-}" == "-p" ]]; then
echo "20"
exit 0
fi
if [[ "${1:-}" == "-v" ]]; then
echo "v20.18.0"
exit 0
fi
exit 0
EOF
chmod +x "${original_bin_dir}/node"
export HOME="${home_dir}"
export PATH="/usr/bin:/bin"
export INSTALL_METHOD="npm"
export SELECTED_NODE_BIN="${selected_bin_dir}/node"
export ORIGINAL_PATH="${original_bin_dir}:/usr/bin:/bin"
ui_warn() { :; }
ensure_user_local_bin_on_path() {
mkdir -p "${HOME}/.local/bin"
export PATH="${HOME}/.local/bin:${PATH}"
}
refresh_shell_command_cache() { hash -r 2>/dev/null || true; }
find_openclaw_entry_path() {
echo "${pkg_dir}/entry.js"
}
install_openclaw_compat_shim
got="$(command -v openclaw || true)"
assert_eq "$got" "${home_dir}/.local/bin/openclaw" "install_openclaw_compat_shim wrapper path"
)
echo "OK"