Skip to content

fix(doctor): detect PATH ordering issues when mise is activated#8585

Merged
jdx merged 5 commits intomainfrom
fix/doctor-path-ordering
Mar 19, 2026
Merged

fix(doctor): detect PATH ordering issues when mise is activated#8585
jdx merged 5 commits intomainfrom
fix/doctor-path-ordering

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 13, 2026

Summary

  • Adds a PATH ordering check to mise doctor that warns when non-mise paths precede mise tool paths in the current PATH
  • Only triggers when mise is activated (not for shims-only setups)
  • Lists the specific paths that take precedence, so the user knows what to fix

Closes #8584

Test plan

  • cargo build succeeds
  • mise run test:e2e test_doctor passes
  • Manual: verify warning appears when PATH has entries before mise tool paths
  • Manual: verify no warning when mise paths are correctly ordered

🤖 Generated with Claude Code


Note

Low Risk
Low risk: adds a new mise doctor warning based on PATH inspection plus an e2e test; no changes to install/auth/data flows.

Overview
mise doctor now checks PATH ordering when mise is activated and warns if any non-mise directories appear before the first mise-managed tool path, listing the specific preceding entries (while ignoring the directory containing the mise binary).

Adds an e2e regression test that prepends a fake system path to PATH to assert the new warning appears, then restores PATH for the remaining doctor checks.

Written by Cursor Bugbot for commit 0fdfc22. This will update automatically on new commits. Configure here.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the mise doctor command by introducing a new diagnostic check for PATH environment variable ordering. The purpose is to proactively identify and inform users about configurations where mise-managed tools might be shadowed by other system-installed tools due to incorrect PATH sequencing. This ensures that mise operates as expected when activated, improving the reliability and user experience by guiding them to correct their shell configurations.

Highlights

  • New Doctor Check: Added a new check to mise doctor to detect issues with the PATH environment variable ordering.
  • PATH Ordering Warning: The check warns users when non-mise paths precede mise tool paths, which could lead to system tools shadowing mise-managed versions.
  • Activation Context: This warning is only triggered when mise is actively activated, not for shims-only setups.
  • Actionable Feedback: The warning message lists the specific paths that take precedence, helping users identify and fix the configuration issue.
Changelog
  • src/cli/doctor/mod.rs
    • Integrated the new check_path_ordering method into the Doctor::run and Doctor::run_with_config methods.
    • Implemented the check_path_ordering async method to analyze the current PATH, identify mise-related paths, and warn if other paths precede them.
Activity
  • Closed a related discussion on GitHub: `mise doctor` doesn't detect path ordering issues #8584.
  • Included a test plan with automated checks for cargo build and mise run test:e2e test_doctor, along with manual verification steps for the warning behavior.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Comment thread src/cli/doctor/mod.rs Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 13, 2026

Greptile Summary

This PR adds a check_path_ordering diagnostic to mise doctor that warns when the user is running with mise activate but non-mise paths appear before mise-managed tool paths in PATH. It is integrated into both the text (doctor) and JSON (doctor_json) output paths, and the e2e test suite is extended with before/after assertions.

Key changes:

  • New check_path_ordering async method on Doctor in src/cli/doctor/mod.rs (lines 484–550): uses ts.final_env + ts.list_final_paths to build the complete set of mise-managed paths (tool installs, env._.path, UV venv, MISE_ADD_PATH), then compares against PATH_NON_PRISTINE to find the first mise path and warn about anything preceding it.
  • The mise binary's own parent directory (e.g. /opt/homebrew/bin) is correctly excluded from the warning via a canonicalized mise_bin_parent comparison, preventing false positives for the common Homebrew install layout.
  • check_path_ordering is correctly wired into both doctor_json (line 123) and analyze_configdoctor (line 336).
  • E2e test (e2e/cli/test_doctor) adds three assertions: no warning with a clean PATH, warning appears when a fake path is prepended, and the fake path appears in the warning text.

Minor observations:

  • The comment on lines 523–524 ("already covered by the activation check") is inaccurate — is_activated() only checks __MISE_DIFF, not whether mise paths are actually present in PATH.
  • The resolve closure is called twice for paths in current_path[..first_mise_idx] (once during position(), once in the filter()), causing duplicate canonicalize syscalls for the same entries.
  • The e2e test's non-existent /tmp/fake-system-bin directory works correctly via the canonicalize fallback but would benefit from a clarifying comment.

Confidence Score: 4/5

  • Safe to merge — the new diagnostic is read-only and additive; no install or execution behavior is changed.
  • The implementation correctly uses list_final_paths (covering all mise-managed path categories including env._.path, UV venv, and MISE_ADD_PATH) and properly canonicalizes mise_bin_parent before comparison. The is_activated() guard ensures the check is skipped for shims-only setups. The remaining issues are minor: a slightly inaccurate comment in the early-return branch and duplicate canonicalize calls for the prefix slice. No correctness, security, or data-integrity risks were identified in the new code.
  • No files require special attention — both changed files are low-risk additions.

Important Files Changed

Filename Overview
src/cli/doctor/mod.rs Adds check_path_ordering async method that uses list_final_paths (covering env._.path, UV venv, MISE_ADD_PATH, and tool paths) and a properly canonicalized mise_bin_parent to detect PATH ordering issues; logic is mostly correct with minor concerns around a misleading comment and duplicate canonicalize calls.
e2e/cli/test_doctor Adds three assertions covering the new PATH ordering check; the test relies on a non-existent /tmp/fake-system-bin directory (canonicalize graceful fallback), which works but benefits from an explanatory comment.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[mise doctor / mise doctor --json] --> B{is_activated?\n__MISE_DIFF set?}
    B -- No --> Z[Skip check]
    B -- Yes --> C[ts.final_env config\nget EnvResults]
    C -- Err --> Z
    C -- Ok --> D[ts.list_final_paths config env_results\nenv._.path + UV venv + MISE_ADD_PATH + tool paths]
    D -- Err --> Z
    D -- Ok, empty --> Z
    D -- Ok, non-empty --> E[Read PATH_NON_PRISTINE\ncurrent process PATH]
    E -- empty --> Z
    E -- non-empty --> F[Build mise_paths_resolved HashSet\ncanonicalised]
    F --> G[Canonicalize MISE_BIN.parent\nmise_bin_parent]
    G --> H[position: first PATH entry\nin mise_paths_resolved]
    H -- None --> Z
    H -- idx == 0 --> Z
    H -- idx > 0 --> I[Filter PATH 0..idx\nexclude mise_bin_parent]
    I -- empty after filter --> Z
    I -- non-empty --> J[Push warning\nmise tool paths are not first in PATH\nlist offending paths]
    J --> K[Warning shown in\ndoctor output / JSON]
Loading

Fix All in Claude Code

Last reviewed commit: 0fdfc22

Comment thread src/cli/doctor/mod.rs Outdated
Comment thread src/cli/doctor/mod.rs Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a useful check to mise doctor to detect and warn about PATH ordering issues when mise is activated. The implementation is solid, but I have a couple of suggestions to improve performance and ensure correctness in path comparisons. Specifically, I recommend using a HashSet for faster lookups of mise paths and ensuring that the mise binary's path is canonicalized before comparison to avoid potential false positives.

Comment thread src/cli/doctor/mod.rs Outdated
Comment on lines +520 to +532
// Check if any paths before the first mise path could shadow mise tools
let preceding_paths: Vec<&PathBuf> = current_path[..first_mise_idx]
.iter()
.filter(|p| {
let resolved = p.canonicalize().unwrap_or_else(|_| (*p).clone());
// Skip paths that are themselves mise paths (e.g. config env._.path entries)
!mise_paths_resolved.contains(&resolved)
// Skip the mise binary's own directory
&& !env::MISE_BIN
.parent()
.is_some_and(|bp| bp == resolved)
})
.collect();
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.

high

This block can be improved for correctness, performance, and consistency:

  1. Correctness: The comparison bp == resolved might fail if env::MISE_BIN is not a canonical path, as resolved is canonicalized but bp is not. This could lead to false positives. Canonicalizing env::MISE_BIN.parent() would fix this.
  2. Performance: It's more efficient to determine the canonicalized parent of MISE_BIN once outside the filter's loop.
  3. Consistency: Using p.clone() is more idiomatic and consistent with other parts of the function than (*p).clone().
Suggested change
// Check if any paths before the first mise path could shadow mise tools
let preceding_paths: Vec<&PathBuf> = current_path[..first_mise_idx]
.iter()
.filter(|p| {
let resolved = p.canonicalize().unwrap_or_else(|_| (*p).clone());
// Skip paths that are themselves mise paths (e.g. config env._.path entries)
!mise_paths_resolved.contains(&resolved)
// Skip the mise binary's own directory
&& !env::MISE_BIN
.parent()
.is_some_and(|bp| bp == resolved)
})
.collect();
// Check if any paths before the first mise path could shadow mise tools
let mise_bin_parent = env::MISE_BIN.parent().and_then(|p| p.canonicalize().ok());
let preceding_paths: Vec<&PathBuf> = current_path[..first_mise_idx]
.iter()
.filter(|p| {
let resolved = p.canonicalize().unwrap_or_else(|_| p.clone());
// Skip paths that are themselves mise paths (e.g. config env._.path entries)
!mise_paths_resolved.contains(&resolved)
// Skip the mise binary's own directory
&& !mise_bin_parent.as_ref().is_some_and(|bp| bp == &resolved)
})
.collect();

Comment thread src/cli/doctor/mod.rs Outdated
Comment on lines +499 to +502
let mise_paths_resolved: Vec<PathBuf> = mise_paths
.iter()
.map(|p| p.canonicalize().unwrap_or_else(|_| p.clone()))
.collect();
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

For performance, it's better to use a HashSet for mise_paths_resolved instead of a Vec. The contains method on a Vec has a linear time complexity (O(n)), while for a HashSet it's constant on average (O(1)). Since contains is called within loops over current_path, this change will significantly improve performance, especially with a large number of mise paths.

Suggested change
let mise_paths_resolved: Vec<PathBuf> = mise_paths
.iter()
.map(|p| p.canonicalize().unwrap_or_else(|_| p.clone()))
.collect();
let mise_paths_resolved: std::collections::HashSet<PathBuf> = mise_paths
.iter()
.map(|p| p.canonicalize().unwrap_or_else(|_| p.clone()))
.collect();

Comment thread src/cli/doctor/mod.rs Outdated
When using `mise activate`, tool paths should appear before system paths
in PATH. Previously, `mise doctor` reported 'ok' even when brew or other
system paths preceded mise paths, causing system tools to shadow
mise-managed versions.

Uses `list_final_paths` to include all mise-managed paths (tool installs,
env._.path, UV venv, MISE_ADD_PATH) in the exclusion set, avoiding false
positives. Canonicalizes MISE_BIN parent path for correct symlink handling.

Closes #8584

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the fix/doctor-path-ordering branch from 79a8716 to 0cbf4e1 Compare March 13, 2026 14:34
Comment thread src/cli/doctor/mod.rs Outdated
Comment thread src/cli/doctor/mod.rs Outdated
jdx and others added 4 commits March 13, 2026 15:38
The mise_bin_parent (e.g. /opt/homebrew/bin) was being included in the
first_mise_idx search, causing it to match early and short-circuit the
PATH ordering check. This masked the exact scenario the diagnostic is
meant to detect. It correctly remains in the preceding_paths filter to
avoid reporting it as a problematic entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The mise_paths_resolved filter was unreachable since first_mise_idx
already guarantees no mise paths appear before it. Simplified to only
filter mise_bin_parent from the display output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests both the positive case (warning when non-mise path is prepended)
and the negative case (no warning when paths are correctly ordered).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Mar 13, 2026

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Comment thread src/cli/doctor/mod.rs
Comment on lines +522 to +526
let Some(first_mise_idx) = first_mise_idx else {
// No mise paths found in current PATH at all — this is already
// covered by the activation check
return;
};
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.

Misleading comment about activation check coverage

The comment says "this is already covered by the activation check," but is_activated() only verifies that __MISE_DIFF is set in the environment — it does not check that mise's tool paths are actually present in PATH_NON_PRISTINE. Reaching this branch means the user is activated but none of the paths returned by list_final_paths appear in their current PATH, which is itself a potentially meaningful diagnostic case (e.g. PATH was reset after _mise_hook ran, or canonicalization mismatches caused the lookup to fail).

Suggested change
let Some(first_mise_idx) = first_mise_idx else {
// No mise paths found in current PATH at all — this is already
// covered by the activation check
return;
};
let Some(first_mise_idx) = first_mise_idx else {
// No mise-managed paths found in the current PATH.
// This can happen when no tools are installed, or when PATH was
// modified after mise activation such that mise paths no longer appear.
return;
};

Fix in Claude Code

Comment thread e2e/cli/test_doctor
Comment on lines +9 to +14
assert_not_contains "mise doctor" "mise tool paths are not first in PATH"
export PATH="/tmp/fake-system-bin:$PATH"
assert_contains "mise doctor" "mise tool paths are not first in PATH"
assert_contains "mise doctor" "/tmp/fake-system-bin"
# Restore PATH for remaining tests
export PATH="${PATH#/tmp/fake-system-bin:}"
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.

Test creates a non-existent PATH directory — canonicalize silently falls back

/tmp/fake-system-bin is added to PATH but never created on disk. Inside check_path_ordering, p.canonicalize() will fail for this entry and the code falls back to p.clone() (the raw, non-canonical path). The lookup then succeeds because no mise-managed path will ever match that literal string, so the test passes.

This is fine by design, but it would be worth a short comment in the test noting this is intentional (a non-existent path) so future readers don't think it's a bug or try to create the directory:

Suggested change
assert_not_contains "mise doctor" "mise tool paths are not first in PATH"
export PATH="/tmp/fake-system-bin:$PATH"
assert_contains "mise doctor" "mise tool paths are not first in PATH"
assert_contains "mise doctor" "/tmp/fake-system-bin"
# Restore PATH for remaining tests
export PATH="${PATH#/tmp/fake-system-bin:}"
# Test PATH ordering warning when non-mise paths precede mise tool paths
# /tmp/fake-system-bin intentionally does not exist on disk;
# check_path_ordering handles non-existent paths gracefully (canonicalize fallback).
assert_not_contains "mise doctor" "mise tool paths are not first in PATH"
export PATH="/tmp/fake-system-bin:$PATH"
assert_contains "mise doctor" "mise tool paths are not first in PATH"
assert_contains "mise doctor" "/tmp/fake-system-bin"
# Restore PATH for remaining tests
export PATH="${PATH#/tmp/fake-system-bin:}"

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

@jdx jdx enabled auto-merge (squash) March 13, 2026 14:57
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 x -- echo 19.9 ± 1.0 18.5 29.1 1.00
mise x -- echo 20.5 ± 1.3 18.9 35.7 1.03 ± 0.08

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 env 19.7 ± 1.1 18.5 28.6 1.00
mise env 20.0 ± 0.9 18.9 25.1 1.01 ± 0.07

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 hook-env 19.9 ± 0.9 18.3 26.4 1.00
mise hook-env 20.4 ± 1.1 19.2 36.1 1.03 ± 0.07

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 ls 20.5 ± 0.9 19.1 26.9 1.00
mise ls 21.3 ± 1.2 19.9 31.2 1.04 ± 0.07

xtasks/test/perf

Command mise-2026.3.8 mise Variance
install (cached) 118ms 117ms +0%
ls (cached) 71ms 70ms +1%
bin-paths (cached) 71ms 72ms -1%
task-ls (cached) 723ms 731ms -1%

@alex-broad
Copy link
Copy Markdown

Looks like the tests failed, it looks like a temporary issue that might be fixed with a retry?

If not, I can fork the repo and branch and see if I can fix it from there?

Appreciate you doing this!

@jdx jdx merged commit 25b2551 into main Mar 19, 2026
50 of 52 checks passed
@jdx jdx deleted the fix/doctor-path-ordering branch March 19, 2026 23:02
jdx pushed a commit that referenced this pull request Mar 21, 2026
### 🐛 Bug Fixes

- **(config)** resolve trust hash collision for same-name directories by
@tdragon in [#8628](#8628)
- **(docs)** fix width of tools table by @himkt in
[#8625](#8625)
- **(docs)** prevent homepage hero atmosphere overflow by @nygmaaa in
[#8642](#8642)
- **(doctor)** detect PATH ordering issues when mise is activated by
@jdx in [#8585](#8585)
- **(git)** use origin as remote name by @bentinata in
[#8626](#8626)
- **(installer)** normalize current version before comparison by @tak848
in [#8649](#8649)
- **(lockfile)** Resolve symlink when updating lockfiles by @chancez in
[#8589](#8589)
- **(python)** verify checksums for precompiled binary downloads by
@malept in [#8593](#8593)
- **(rust)** resolve relative CARGO_HOME/RUSTUP_HOME to absolute paths
by @simonepri in [#8604](#8604)
- **(task)** correctly resolve task name for _default files with
extensions by @youta1119 in
[#8646](#8646)
- **(tasks)** global file tasks not properly marked as such by @roele in
[#8618](#8618)
- **(tasks)** handle broken pipe in table print and task completion
output by @vmaleze in [#8608](#8608)
- use dark/light logo variants in README for GitHub dark mode by @jdx in
[#8656](#8656)
- failing rebuild of runtime symlinks for shared tools by @roele in
[#8647](#8647)
- add spaces around current element operator in flutter version_expr by
@roele in [#8616](#8616)
- complete task arguments correctly by @KevSlashNull in
[#8601](#8601)

### 📚 Documentation

- switch body font to DM Sans and darken dark mode background by @jdx in
[6e3ad34](6e3ad34)
- use Cormorant Garamond for headers and Roc Grotesk for body text by
@jdx in
[010812a](010812a)
- resolve chaotic heading hierarchy in task-arguments.md by @muzimuzhi
in [#8644](#8644)

### 🧪 Testing

- fix test_java and mark as slow by @roele in
[#8634](#8634)

### 📦️ Dependency Updates

- update docker/dockerfile:1 docker digest to 4a43a54 by @renovate[bot]
in [#8657](#8657)
- update ghcr.io/jdx/mise:alpine docker digest to 2584470 by
@renovate[bot] in [#8658](#8658)

### 📦 Registry

- add viteplus (npm:vite-plus) by @risu729 in
[#8594](#8594)
- remove backend.options for podman by @roele in
[#8633](#8633)
- add pi.dev coding agent by @dector in
[#8635](#8635)
- add ormolu ([github:tweag/ormolu](https://github.com/tweag/ormolu)) by
@3w36zj6 in [#8617](#8617)
- use version_expr for dart and sort versions by @roele in
[#8631](#8631)

### New Contributors

- @bentinata made their first contribution in
[#8626](#8626)
- @tdragon made their first contribution in
[#8628](#8628)
- @nygmaaa made their first contribution in
[#8642](#8642)
- @youta1119 made their first contribution in
[#8646](#8646)
- @chancez made their first contribution in
[#8589](#8589)
- @dector made their first contribution in
[#8635](#8635)
- @tak848 made their first contribution in
[#8649](#8649)

## 📦 Aqua Registry Updates

#### New Packages (5)

- [`acsandmann/rift`](https://github.com/acsandmann/rift)
-
[`alltuner/mise-completions-sync`](https://github.com/alltuner/mise-completions-sync)
- [`berbicanes/apiark`](https://github.com/berbicanes/apiark)
-
[`gitlab.com/graphviz/graphviz`](https://github.com/gitlab.com/graphviz/graphviz)
-
[`jorgelbg/pinentry-touchid`](https://github.com/jorgelbg/pinentry-touchid)

#### Updated Packages (7)

- [`UpCloudLtd/upcloud-cli`](https://github.com/UpCloudLtd/upcloud-cli)
- [`aquaproj/registry-tool`](https://github.com/aquaproj/registry-tool)
- [`go-swagger/go-swagger`](https://github.com/go-swagger/go-swagger)
-
[`gopinath-langote/1build`](https://github.com/gopinath-langote/1build)
- [`sassman/t-rec-rs`](https://github.com/sassman/t-rec-rs)
- [`sharkdp/fd`](https://github.com/sharkdp/fd)
- [`temporalio/cli`](https://github.com/temporalio/cli)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants