From ed5365859c37e77c78cb79cc5637032c419a33e8 Mon Sep 17 00:00:00 2001 From: Jeffrey Date: Thu, 6 Aug 2026 14:25:52 +0800 Subject: [PATCH] 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. --- .zshenv | 18 ++++++++++++++++++ .zshrc | 7 ++----- README.md | 5 +++-- tests/installers.sh | 10 ++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.zshenv b/.zshenv index a4df88a..f2df2a7 100644 --- a/.zshenv +++ b/.zshenv @@ -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 diff --git a/.zshrc b/.zshrc index 20ce357..e0c2e5f 100644 --- a/.zshrc +++ b/.zshrc @@ -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 diff --git a/README.md b/README.md index 1ec5f13..ae13a81 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/tests/installers.sh b/tests/installers.sh index 00fe2e0..6ea0b07 100755 --- a/tests/installers.sh +++ b/tests/installers.sh @@ -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"