fix(zshenv): put ~/.local/bin on PATH for every zsh

Non-interactive agent shells (Codex, Claude) skip .zshrc and never saw
user-local tools installed via pipx/uv/grok. Add ~/.local/bin in
.zshenv with an idempotent guard, and source optional
~/.config/zshenv.local for machine-local early env.
This commit is contained in:
Jeffrey
2026-08-06 14:25:52 +08:00
parent 6f6a8ac5f8
commit ed5365859c
4 changed files with 33 additions and 7 deletions
+18
View File
@@ -6,3 +6,21 @@ if [ -x /opt/homebrew/bin/brew ]; then
elif [ -x /usr/local/bin/brew ]; then
eval "$(cd /tmp && /usr/local/bin/brew shellenv)"
fi
# User-local binaries (pipx, uv tools, grok CLI, etc.).
# Lives here rather than .zshrc/.zprofile so non-interactive agent shells
# (Codex, Claude Code, CI zsh -c) still resolve user-installed tools.
if [ -d "$HOME/.local/bin" ]; then
case ":${PATH}:" in
*":$HOME/.local/bin:"*) ;;
*) PATH="$HOME/.local/bin:${PATH}" ;;
esac
export PATH
fi
# Machine-local early env (cargo, private PATH bits). Survives easyzsh reinstalls
# of this file. Prefer this over editing ~/.zshenv by hand.
if [ -f "$HOME/.config/zshenv.local" ]; then
# shellcheck disable=SC1090,SC1091
. "$HOME/.config/zshenv.local"
fi
+2 -5
View File
@@ -136,11 +136,8 @@ plugins=(
# alias zshconfig="mate ~/.zshrc"
# alias ohmyzsh="mate ~/.oh-my-zsh"
# Add to PATH to run programs installed with pipx or "pip install --user"
# export PATH=$PATH:~/.local/bin
# To give this path preference instead of system paths to run the latest version of tools, add the following to your personal config. Due to security concerns this is not done by default.
# export PATH=~/.local/bin:$PATH
# ~/.local/bin is added in ~/.zshenv (every zsh, including non-interactive agents).
# Do not re-export it here; keep personal early-env overrides in ~/.config/zshenv.local.
# export PATH=$PATH:~/.config/ezsh/bin
+3 -2
View File
@@ -19,10 +19,11 @@ and `~/.zshenv` before replacing them.
| Path | Role |
| --- | --- |
| `~/.zshrc` | Canonical shell entry (from repo `.zshrc`). Stay close to upstream; reinstall overwrites it. |
| `~/.zshenv` | Early env for every zsh (Homebrew `shellenv` on macOS). |
| `~/.zshenv` | Early env for every zsh: Homebrew `shellenv` on macOS, plus `~/.local/bin` on PATH. |
| `~/.config/zshenv.local` | Optional machine-local early env (cargo, private PATH). Sourced by `~/.zshenv`; not overwritten by reinstall. |
| `~/.config/zshrc/*.zsh` | Your overlays, sourced by `~/.zshrc` **before** oh-my-zsh loads. |
Do not put personal tweaks into `~/.zshrc` if you want painless upgrades: put them under `~/.config/zshrc/` instead.
Do not put personal tweaks into `~/.zshrc` if you want painless upgrades: interactive overlays go under `~/.config/zshrc/`; early PATH/env that non-interactive agents need go in `~/.config/zshenv.local`.
## Personal configuration
+10
View File
@@ -88,9 +88,19 @@ grep -Fq 'cp -Pp "$HOME/.zshenv"' "$ROOT/install.sh" || fail ".zshenv symlinks a
grep -Fq '[ -L "$HOME/.zshenv" ]' "$ROOT/install.sh" || fail "broken .zshenv symlinks are not backed up"
grep -Fq 'brew shellenv' "$ROOT/.zshenv" || fail "Homebrew is not initialized in .zshenv"
grep -Fq '/usr/local/bin/brew' "$ROOT/.zshenv" || fail "Intel Homebrew path is missing from .zshenv"
grep -Fq '$HOME/.local/bin' "$ROOT/.zshenv" || fail "~/.local/bin is not added in .zshenv"
grep -Fq 'zshenv.local' "$ROOT/.zshenv" || fail "machine-local zshenv.local hook is missing"
if grep -Fq 'brew shellenv' "$ROOT/.zshrc"; then
fail "Homebrew still initializes from interactive-only .zshrc"
fi
# Non-interactive zsh must resolve ~/.local/bin without sourcing .zshrc.
local_bin_home="$TEMP_DIR/local-bin-home"
mkdir -p "$local_bin_home/.local/bin"
printf '%s\n' '#!/bin/sh' 'echo ok' >"$local_bin_home/.local/bin/easyzsh-path-probe"
chmod +x "$local_bin_home/.local/bin/easyzsh-path-probe"
HOME="$local_bin_home" PATH=/usr/bin:/bin \
zsh -c '. "$1"; command -v easyzsh-path-probe >/dev/null' zsh "$ROOT/.zshenv" ||
fail "~/.local/bin is not available to non-interactive zsh via .zshenv"
[[ ! -e "$ROOT/install/fnm.sh" ]] || fail "vendored fnm installer still exists"
if grep -Eq '^[[:space:]]*export MLX_USER_TOKEN=' "$ROOT"/zshrc/*.zsh; then
fail "a static MLX user token remains"