fix(zsh): stop lazy-loading nvm, source it eagerly instead

The lazy-load stub wired nvm/node/npm/npx/corepack as wrapper functions
that called a private helper, _nvm_lazy_load, to source nvm.sh on first
real use. That helper went missing in some non-interactive shell contexts
(e.g. a coding agent's captured shell snapshot only kept the public
wrapper functions, not the underscore-prefixed helper), leaving the
wrappers calling a function that no longer existed - infinite recursion
via FUNCNEST, every npm/node/nvm invocation broken.

Sourcing nvm.sh eagerly costs 300-700ms on shell startup but has no
moving parts to go missing.
This commit is contained in:
2026-08-04 12:22:45 +03:00
parent 20df614d54
commit 9d3f45a821
+8 -10
View File
@@ -150,23 +150,21 @@ alias ll='eza -lh --git --color-scale=all --color=auto --icons=auto --hyperlink=
alias la='eza -lAh --git --color-scale=all --color=auto --icons=auto --hyperlink=auto --group-directories-first' alias la='eza -lAh --git --color-scale=all --color=auto --icons=auto --hyperlink=auto --group-directories-first'
alias lt='eza --tree --level=2 --color=auto --icons=auto --hyperlink=auto --group-directories-first' alias lt='eza --tree --level=2 --color=auto --icons=auto --hyperlink=auto --group-directories-first'
# Lazy-load nvm: sourcing nvm.sh eagerly costs 300-700ms on every shell # nvm: the lazy-load stub (wrapper functions deferring to a helper that
# startup even when node isn't used that session. These stubs load the # sourced nvm.sh on first real use) was unreliable in some non-interactive
# real nvm on first actual use, then get out of the way. # shell contexts, where the wrapper could run without the helper function
# being defined yet. Sourcing eagerly costs 300-700ms on shell startup but
# always works.
export NVM_DIR="$HOME/.nvm" export NVM_DIR="$HOME/.nvm"
_nvm_lazy_load() { () {
unset -f nvm node npm npx corepack
# oh-my-zsh sets extendedglob, which breaks nvm's `lts/*`-style alias # oh-my-zsh sets extendedglob, which breaks nvm's `lts/*`-style alias
# globs and silently kills auto-activation of the default version. # globs and silently kills auto-activation of the default version.
# local_options scopes this to the anonymous function, restoring the
# prior setting once nvm.sh is sourced.
setopt local_options no_extended_glob setopt local_options no_extended_glob
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
} }
nvm() { _nvm_lazy_load; nvm "$@"; }
node() { _nvm_lazy_load; node "$@"; }
npm() { _nvm_lazy_load; npm "$@"; }
npx() { _nvm_lazy_load; npx "$@"; }
corepack() { _nvm_lazy_load; corepack "$@"; }
# Lazy-load pyenv's shell integration (~130-150ms here). Shims are # Lazy-load pyenv's shell integration (~130-150ms here). Shims are
# already on PATH via .zprofile, so `python`/`pip` work immediately # already on PATH via .zprofile, so `python`/`pip` work immediately