Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions e2e/env/test_path_reorder_after_activate
Original file line number Diff line number Diff line change
@@ -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)"

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which is non-POSIX and is flagged by ShellCheck (SC2230). Since hk.pkl runs shellcheck over e2e/**, this will break linting in CI. Prefer command -v mise (and fail with a clear message if it’s not found) when capturing the absolute path before overriding PATH.

Suggested change
MISE_BIN="$(which mise)"
MISE_BIN="$(command -v mise)"
if [[ -z "$MISE_BIN" ]]; then
echo "FAIL: mise binary not found in PATH" >&2
exit 1
fi

Copilot uses AI. Check for mistakes.

# 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)
Comment on lines +35 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The grep commands here could lead to incorrect matches if other paths in PATH contain "mytool/bin" or "system-bin" as substrings. To make the test more robust, it's better to perform an exact match on the full path strings.

You can use grep -F -x for an exact, full-line match. Since the command is in double quotes, $HOME will be expanded correctly.

MYTOOL_POS=$(echo "$PATH" | tr ':' '\n' | grep -n -F -x "$HOME/mytool/bin" | head -1 | cut -d: -f1)
SYSTEM_POS=$(echo "$PATH" | tr ':' '\n' | grep -n -F -x "$HOME/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
19 changes: 17 additions & 2 deletions src/cli/hook_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 &current_paths {
if orig_set.contains(path) {
seen_orig = true;
orig_reordered.push(path.clone());
seen_in_current.insert(path);
continue;
}

Expand All @@ -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![]),
};
Expand Down
Loading