diff --git a/e2e/env/test_path_reorder_after_activate b/e2e/env/test_path_reorder_after_activate new file mode 100644 index 0000000000..cabf87a8ea --- /dev/null +++ b/e2e/env/test_path_reorder_after_activate @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Test that PATH reordering done after `mise activate` is preserved by hook-env. +# This simulates the scenario where ~/.zlogin (which runs after ~/.zshrc on login +# shells) reorders PATH entries — e.g., moving a tool to the front for priority. +# +# Regression test for https://github.com/jdx/mise/discussions/8188 +# and https://github.com/jdx/mise/issues/8168 + +# Save the mise binary path before we override PATH +MISE_BIN="$(which mise)" + +# Simulate the PATH captured during mise activate in ~/.zshrc +# The original order has system-bin first, mytool/bin last +export __MISE_ORIG_PATH="$HOME/system-bin:/usr/local/bin:/usr/bin:/bin:$HOME/mytool/bin" + +# Create test directories and executables +mkdir -p "$HOME/mytool/bin" +mkdir -p "$HOME/system-bin" + +# Simulate ~/.zlogin moving ~/mytool/bin to the FRONT of PATH +# (this is what users do to override system binaries) +export PATH="$HOME/mytool/bin:$HOME/system-bin:/usr/local/bin:/usr/bin:/bin" + +cat >mise.toml <<'EOF' +EOF + +# Run hook-env using the saved binary path +eval "$("$MISE_BIN" hook-env -s bash)" + +echo "DEBUG: PATH=$PATH" + +# ~/mytool/bin should still be BEFORE ~/system-bin because the user +# reordered it to the front after activation +MYTOOL_POS=$(echo "$PATH" | tr ':' '\n' | grep -n "mytool/bin" | head -1 | cut -d: -f1) +SYSTEM_POS=$(echo "$PATH" | tr ':' '\n' | grep -n "system-bin" | head -1 | cut -d: -f1) + +echo "DEBUG: mytool/bin at position $MYTOOL_POS, system-bin at position $SYSTEM_POS" + +if [[ -z $MYTOOL_POS ]]; then + echo "FAIL: mytool/bin not found in PATH" + exit 1 +fi + +if [[ -z $SYSTEM_POS ]]; then + echo "FAIL: system-bin not found in PATH" + exit 1 +fi + +if [[ $MYTOOL_POS -lt $SYSTEM_POS ]]; then + echo "SUCCESS: mytool/bin stays before system-bin (reorder preserved)" +else + echo "FAIL: mytool/bin ($MYTOOL_POS) was moved after system-bin ($SYSTEM_POS)" + echo "hook-env restored the original __MISE_ORIG_PATH order instead of the current order" + exit 1 +fi diff --git a/src/cli/hook_env.rs b/src/cli/hook_env.rs index 0b1570aa10..32e01cae53 100644 --- a/src/cli/hook_env.rs +++ b/src/cli/hook_env.rs @@ -244,12 +244,20 @@ impl HookEnv { // (after the original PATH entries) to preserve their intended position. // This prevents paths appended after `mise activate` in shell rc from // being moved to the front of PATH. + // + // Also collect orig paths in their current order to preserve any + // reordering done after activation (e.g., by ~/.zlogin which runs + // after ~/.zshrc where mise activate is typically placed). let mut pre = Vec::new(); let mut post_user = Vec::new(); + let mut orig_reordered = Vec::new(); let mut seen_orig = false; + let mut seen_in_current: HashSet<&PathBuf> = HashSet::new(); for path in ¤t_paths { if orig_set.contains(path) { seen_orig = true; + orig_reordered.push(path.clone()); + seen_in_current.insert(path); continue; } @@ -266,8 +274,15 @@ impl HookEnv { } } - // Use the original PATH directly as "post" to ensure it's preserved exactly - (pre, orig_paths, post_user) + // Append any orig paths that are no longer in current PATH + // (to avoid losing paths that may have been temporarily removed) + for path in &orig_paths { + if !seen_in_current.contains(path) { + orig_reordered.push(path.clone()); + } + } + + (pre, orig_reordered, post_user) } _ => (vec![], current_paths, vec![]), };