Conversation
…onfig_root\n\nfix(config): resolve env._.path relative to config_root; add process-local cache for config_root and reset it on Settings::reset()\n\nrefactor(config): move config_root logic into config_root.rs and update imports\n\n test(e2e): add test_env_path_resolution to verify path resolution against config_root
There was a problem hiding this comment.
Pull Request Overview
This PR fixes path resolution inconsistency in environment directives by ensuring env._.path uses the same config_root logic as other directives. The main change extracts the config_root function into its own module with caching and updates all references to use the centralized implementation.
- Moves
config_rootfunction to dedicated module with caching for performance - Updates
env._.pathresolution to useconfig_rootinstead of simple parent directory - Adds comprehensive test coverage and documentation for config root behavior
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/config/config_file/config_root.rs | New module containing extracted config_root function with caching |
| src/config/env_directive/mod.rs | Updates path resolution to use config_root for consistency |
| src/config/config_file/mod.rs | Removes old config_root function and updates references |
| src/config/config_file/mise_toml.rs | Updates import to use new config_root module |
| src/config/settings.rs | Adds cache reset when settings are reset |
| e2e/env/test_env_path_resolution | New test demonstrating path resolution behavior |
| docs/environments/index.md | Documents config_root concept and updated path resolution |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| let parent_path = || path.parent().unwrap().to_path_buf(); | ||
| let grandparent_path = || parent_path().parent().unwrap().to_path_buf(); | ||
| let great_grandparent_path = || grandparent_path().parent().unwrap().to_path_buf(); | ||
| let great_great_grandparent_path = || great_grandparent_path().parent().unwrap().to_path_buf(); | ||
| let is_mise_dir = |d: &str| d == "mise" || d == ".mise"; | ||
| let is_config_filename = |f: &str| { | ||
| f == "config.toml" || f == "config.local.toml" || regex!(r"config\..+\.toml").is_match(f) | ||
| }; | ||
| if parent == "conf.d" && is_mise_dir(grandparent) { | ||
| if great_grandparent == ".config" { | ||
| let out = great_great_grandparent_path(); | ||
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | ||
| return out; | ||
| } else { | ||
| let out = great_grandparent_path(); | ||
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | ||
| return out; | ||
| } | ||
| } else if is_mise_dir(parent) && is_config_filename(filename) { | ||
| if grandparent == ".config" { | ||
| let out = great_grandparent_path(); | ||
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | ||
| return out; | ||
| } else { | ||
| let out = grandparent_path(); | ||
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | ||
| return out; |
There was a problem hiding this comment.
These closures use .unwrap() which will panic if any parent directory doesn't exist. Consider using proper error handling or returning a Result type to handle cases where the path hierarchy is incomplete.
| let parent_path = || path.parent().unwrap().to_path_buf(); | |
| let grandparent_path = || parent_path().parent().unwrap().to_path_buf(); | |
| let great_grandparent_path = || grandparent_path().parent().unwrap().to_path_buf(); | |
| let great_great_grandparent_path = || great_grandparent_path().parent().unwrap().to_path_buf(); | |
| let is_mise_dir = |d: &str| d == "mise" || d == ".mise"; | |
| let is_config_filename = |f: &str| { | |
| f == "config.toml" || f == "config.local.toml" || regex!(r"config\..+\.toml").is_match(f) | |
| }; | |
| if parent == "conf.d" && is_mise_dir(grandparent) { | |
| if great_grandparent == ".config" { | |
| let out = great_great_grandparent_path(); | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } else { | |
| let out = great_grandparent_path(); | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } | |
| } else if is_mise_dir(parent) && is_config_filename(filename) { | |
| if grandparent == ".config" { | |
| let out = great_grandparent_path(); | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } else { | |
| let out = grandparent_path(); | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| let parent_path = || path.parent().map(|p| p.to_path_buf()); | |
| let grandparent_path = || parent_path().and_then(|p| p.parent().map(|gp| gp.to_path_buf())); | |
| let great_grandparent_path = || grandparent_path().and_then(|p| p.parent().map(|ggp| ggp.to_path_buf())); | |
| let great_great_grandparent_path = || great_grandparent_path().and_then(|p| p.parent().map(|gggp| gggp.to_path_buf())); | |
| let is_mise_dir = |d: &str| d == "mise" || d == ".mise"; | |
| let is_config_filename = |f: &str| { | |
| f == "config.toml" || f == "config.local.toml" || regex!(r"config\..+\.toml").is_match(f) | |
| }; | |
| if parent == "conf.d" && is_mise_dir(grandparent) { | |
| if great_grandparent == ".config" { | |
| if let Some(out) = great_great_grandparent_path() { | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } else { | |
| // fallback: return original path if hierarchy is incomplete | |
| return path.clone(); | |
| } | |
| } else { | |
| if let Some(out) = great_grandparent_path() { | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } else { | |
| return path.clone(); | |
| } | |
| } | |
| } else if is_mise_dir(parent) && is_config_filename(filename) { | |
| if grandparent == ".config" { | |
| if let Some(out) = great_grandparent_path() { | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } else { | |
| return path.clone(); | |
| } | |
| } else { | |
| if let Some(out) = grandparent_path() { | |
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); | |
| return out; | |
| } else { | |
| return path.clone(); | |
| } |
| let out = great_great_grandparent_path(); | ||
| CONFIG_ROOT_CACHE.lock().unwrap().insert(path, out.clone()); |
There was a problem hiding this comment.
[nitpick] The cache insertion logic is duplicated across multiple branches. Consider extracting this into a helper function that takes the computed path and handles both the cache insertion and return.
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.2 x -- echo |
18.4 ± 0.3 | 17.9 | 22.1 | 1.00 ± 0.02 |
mise x -- echo |
18.4 ± 0.2 | 17.9 | 20.0 | 1.00 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.2 env |
17.8 ± 0.2 | 17.3 | 18.5 | 1.00 |
mise env |
18.2 ± 0.6 | 17.4 | 22.0 | 1.02 ± 0.03 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.2 hook-env |
17.7 ± 0.4 | 17.1 | 18.9 | 1.01 ± 0.04 |
mise hook-env |
17.6 ± 0.6 | 17.1 | 25.4 | 1.00 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.2 ls |
15.9 ± 0.2 | 15.5 | 17.5 | 1.00 |
mise ls |
15.9 ± 0.2 | 15.4 | 18.0 | 1.01 ± 0.02 |
xtasks/test/perf
| Command | mise-2025.9.2 | mise | Variance |
|---|---|---|---|
| install (cached) | 163ms | ✅ 100ms | +63% |
| ls (cached) | 61ms | 60ms | +1% |
| bin-paths (cached) | 65ms | 64ms | +1% |
| task-ls (cached) | 478ms | 465ms | +2% |
✅ Performance improvement: install cached is 63%
### 🚀 Features - **(backend)** improve http error when platform url missing; list available platforms by @jdx in [#6200](#6200) ### 🐛 Bug Fixes - **(backend)** preserve arch underscores in platform keys by @jdx in [#6202](#6202) - **(task)** resolve hanging issue with multiple depends_post by @jdx in [#6206](#6206) - couldn't download node binary in Alpine, even if it exists in the mirror url by @Hazer in [#5972](#5972) - **breaking** use config_root for env._.path by @jdx in [#6204](#6204) - bugfix for paths that include spaces by @karim-elkholy in [#6210](#6210) ### 📚 Documentation - improve release notes generation by @jdx in [#6197](#6197) - fix release changelog contributor reporting by @jdx in [#6201](#6201) ### Chore - use fine-grained gh token by @jdx in [#6208](#6208) - use settings.local.json for claude config by @jdx in [fd0fba9](fd0fba9) ### New Contributors - @karim-elkholy made their first contribution in [#6210](#6210) - @Hazer made their first contribution in [#5972](#5972)
## [2025.9.5](https://github.com/jdx/mise/compare/v2025.9.4..v2025.9.5) - 2025-09-06 ### 🚀 Features - **(task)** add timeout support for task execution by @jdx in [#6216](jdx/mise#6216) - **(task)** sub-tasks in run lists by @jdx in [#6212](jdx/mise#6212) ### Chore - fix npm publish action by @jdx in [14f4b09](jdx/mise@14f4b09) - fix cloudflare release action by @jdx in [00afa25](jdx/mise@00afa25) - fix git-cliff for release notes by @jdx in [15a9aed](jdx/mise@15a9aed) ## [2025.9.4](https://github.com/jdx/mise/compare/v2025.9.3..v2025.9.4) - 2025-09-06 ### Chore - fix git-cliff on release by @jdx in [3c388f2](jdx/mise@3c388f2) ## [2025.9.3](https://github.com/jdx/mise/compare/v2025.9.2..v2025.9.3) - 2025-09-06 ### 🚀 Features - **(backend)** improve http error when platform url missing; list available platforms by @jdx in [#6200](jdx/mise#6200) - **(cli)** support scoped packages for all backend types by @earlgray283 in [#6213](jdx/mise#6213) - **(http)** add URL replacement feature for HTTP requests by @ThomasSteinbach in [#6207](jdx/mise#6207) ### 🐛 Bug Fixes - **(backend)** preserve arch underscores in platform keys by @jdx in [#6202](jdx/mise#6202) - **(task)** resolve hanging issue with multiple depends_post by @jdx in [#6206](jdx/mise#6206) - couldn't download node binary in Alpine, even if it exists in the mirror url by @Hazer in [#5972](jdx/mise#5972) - **breaking** use config_root for env._.path by @jdx in [#6204](jdx/mise#6204) - bugfix for paths that include spaces by @karim-elkholy in [#6210](jdx/mise#6210) ### 📚 Documentation - improve release notes generation by @jdx in [#6197](jdx/mise#6197) - fix release changelog contributor reporting by @jdx in [#6201](jdx/mise#6201) ### Chore - use fine-grained gh token by @jdx in [#6208](jdx/mise#6208) - use settings.local.json for claude config by @jdx in [fd0fba9](jdx/mise@fd0fba9) ### New Contributors - @ThomasSteinbach made their first contribution in [#6207](jdx/mise#6207) - @earlgray283 made their first contribution in [#6213](jdx/mise#6213) - @karim-elkholy made their first contribution in [#6210](jdx/mise#6210) - @Hazer made their first contribution in [#5972](jdx/mise#5972) ## [2025.9.2](https://github.com/jdx/mise/compare/v2025.9.1..v2025.9.2) - 2025-09-05 ### 🐛 Bug Fixes - **(ci)** set required environment variables for npm publishing by @jdx in [#6189](jdx/mise#6189) - **(release)** clean up extra newlines in release notes formatting by @jdx in [#6190](jdx/mise#6190) - **(release)** add proper newline after New Contributors section in cliff template by @jdx in [#6194](jdx/mise#6194) - **(release)** fix changelog formatting to remove extra blank lines by @jdx in [#6195](jdx/mise#6195) - **(release)** restore proper newline after New Contributors section by @jdx in [#6196](jdx/mise#6196) ### 🚜 Refactor - **(ci)** split release workflow into separate specialized workflows by @jdx in [#6193](jdx/mise#6193) ### Chore - **(release)** require GitHub Actions environment for release-plz script by @jdx in [#6191](jdx/mise#6191) ## [2025.9.1](https://github.com/jdx/mise/compare/v2025.9.0..v2025.9.1) - 2025-09-05 ### 🐛 Bug Fixes - python nested venv path order by @elvismacak in [#6124](jdx/mise#6124) - resolve immutable release workflow and VERSION file timing issues by @jdx in [#6187](jdx/mise#6187) ### New Contributors - @elvismacak made their first contribution in [#6124](jdx/mise#6124) ## [2025.9.0](https://github.com/jdx/mise/compare/v2025.8.21..v2025.9.0) - 2025-09-05 ### 🚀 Features - allow set/unset backend aliases by @roele in [#6172](jdx/mise#6172) ### 🐛 Bug Fixes - **(aqua)** respect order of asset_strs by @risu729 in [#6143](jdx/mise#6143) - **(java)** treat freebsd as linux (assuming linux compatability) by @roele in [#6161](jdx/mise#6161) - **(nushell/windows)** Fix $env.PATH getting converted to a string by @zackyancey in [#6157](jdx/mise#6157) - **(sync)** create uv_versions_path dir if it doesn't exist by @risu729 in [#6142](jdx/mise#6142) - **(ubi)** show relevent error messages for v-prefixed tags by @risu729 in [#6183](jdx/mise#6183) - remove nodejs/golang alias migrate code by @risu729 in [#6141](jdx/mise#6141) - mise activate not working on powershell v5 by @L0RD-ZER0 in [#6168](jdx/mise#6168) ### 📚 Documentation - **(task)** remove word "additional" to avoid confusions by @risu729 in [#6159](jdx/mise#6159) ### Chore - update Cargo.lock by @risu729 in [#6184](jdx/mise#6184) ### New Contributors - @zackyancey made their first contribution in [#6157](jdx/mise#6157)
The behavior and docs for env..path predates config_root as well as config files outside of the root like
.config/mise.toml. This has been a bug and should have been fixed when support was added for files outside the project root but was not. This change modifies the behavior of env..path to behave the same as other parts of mise that are relative to config_root and not the parent directory of the config file making mise behave consistently in regards to relative paths.Fixes #6203
BREAKING CHANGE: env..path directories are now relative to the config_root and not the parent directory of the config file. This only affects config files that use env..path that are outside the config_root (e.g.:
.config/mise.tomlwill use.and not.configlike before). To retain compatibility with earlier mise releases, be explicit withenv._.path = "{{config_root}}/bin"which will behave the same in old and new releases.