fix(doctor): detect PATH ordering issues when mise is activated#8585
fix(doctor): detect PATH ordering issues when mise is activated#8585
Conversation
Summary of ChangesHello, 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 Highlights
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
Greptile SummaryThis PR adds a Key changes:
Minor observations:
Confidence Score: 4/5
Important Files Changed
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]
Last reviewed commit: 0fdfc22 |
There was a problem hiding this comment.
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.
| // 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(); |
There was a problem hiding this comment.
This block can be improved for correctness, performance, and consistency:
- Correctness: The comparison
bp == resolvedmight fail ifenv::MISE_BINis not a canonical path, asresolvedis canonicalized butbpis not. This could lead to false positives. Canonicalizingenv::MISE_BIN.parent()would fix this. - Performance: It's more efficient to determine the canonicalized parent of
MISE_BINonce outside the filter's loop. - Consistency: Using
p.clone()is more idiomatic and consistent with other parts of the function than(*p).clone().
| // 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(); |
| let mise_paths_resolved: Vec<PathBuf> = mise_paths | ||
| .iter() | ||
| .map(|p| p.canonicalize().unwrap_or_else(|_| p.clone())) | ||
| .collect(); |
There was a problem hiding this comment.
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.
| 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(); |
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>
79a8716 to
0cbf4e1
Compare
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>
|
bugbot run |
| 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; | ||
| }; |
There was a problem hiding this comment.
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).
| 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; | |
| }; |
| 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:}" |
There was a problem hiding this comment.
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:
| 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!
Hyperfine Performance
|
| 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% |
|
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! |
### 🐛 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)
Summary
mise doctorthat warns when non-mise paths precede mise tool paths in the current PATHCloses #8584
Test plan
cargo buildsucceedsmise run test:e2e test_doctorpasses🤖 Generated with Claude Code
Note
Low Risk
Low risk: adds a new
mise doctorwarning based on PATH inspection plus an e2e test; no changes to install/auth/data flows.Overview
mise doctornow 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 themisebinary).Adds an e2e regression test that prepends a fake system path to
PATHto assert the new warning appears, then restoresPATHfor the remaining doctor checks.Written by Cursor Bugbot for commit 0fdfc22. This will update automatically on new commits. Configure here.