fix(task): stable MISE_PROJECT_ROOT for monorepo tasks, add MISE_MONOREPO_ROOT#9657
fix(task): stable MISE_PROJECT_ROOT for monorepo tasks, add MISE_MONOREPO_ROOT#9657
Conversation
…REPO_ROOT For monorepo tasks, MISE_PROJECT_ROOT was inconsistent: from inside the subproject it pointed at the subproject directory, but from the monorepo root or an intermediate directory it pointed at the monorepo root, because it was sourced from `config.project_root` (cwd-dependent) before falling back to the task's own `config_root`. Prefer the task's own `config_root` so MISE_PROJECT_ROOT is the directory of the mise.toml that defined the task, regardless of where the task was invoked from. Also expose MISE_MONOREPO_ROOT so tasks can reference the monorepo root directly without needing a cwd-dependent fallback. Refs #9639
There was a problem hiding this comment.
Code Review
This pull request introduces the MISE_MONOREPO_ROOT environment variable and modifies the logic for MISE_PROJECT_ROOT to prioritize the task's configuration root, ensuring consistent behavior in monorepo environments. Feedback indicates a potential regression where global or remote tasks might now incorrectly use the global configuration directory as the project root instead of the current project's root.
| if let Some(root) = task.config_root.clone().or(config.project_root.clone()) { | ||
| env.insert("MISE_PROJECT_ROOT".into(), root.display().to_string()); | ||
| } |
There was a problem hiding this comment.
Preferring task.config_root for MISE_PROJECT_ROOT provides the desired stability for monorepo subproject tasks, but it introduces a regression for global and remote tasks.
For these tasks, task.config_root points to the global configuration directory (e.g., ~/.config/mise) or a remote cache directory. Previously, MISE_PROJECT_ROOT would fall back to the current project's root (config.project_root), which is typically what a global task expects when operating on the current project. With this change, MISE_PROJECT_ROOT will now point to the global/remote directory instead, which may break tasks that rely on this variable to locate project files.
It is recommended to preserve the original preference for global and remote tasks.
let root = if task.global || task.is_remote() {
config.project_root.as_ref().or(task.config_root.as_ref())
} else {
task.config_root.as_ref().or(config.project_root.as_ref())
};
if let Some(root) = root {
env.insert("MISE_PROJECT_ROOT".into(), root.display().to_string());
}
Greptile SummaryThis PR fixes
Confidence Score: 5/5Safe to merge. The precedence change is scoped to non-global, non-remote tasks and is a no-op for single-project repos where task.config_root == config.project_root. The guard using task.global covers every global-task loading path (load_config_tasks via is_global_config, and load_tasks_includes via is_global_task_include_path + mark_tasks_as_global), so the regression risk for global tasks is eliminated. For non-monorepo projects task.config_root and config.project_root are the same value, so existing behaviour is preserved. The new MISE_MONOREPO_ROOT variable is additive and gated behind the existing experimental flag. No files require special attention. Reviews (3): Last reviewed commit: "fix(task): use task.global to detect glo..." | Re-trigger Greptile |
Greptile flagged that preferring task.config_root unconditionally broke MISE_PROJECT_ROOT for tasks defined in the global mise config: task.config_root for those points at the global config dir (e.g. ~/.config/mise) instead of the local project the task is being run from. For global/system-config tasks, keep the original precedence (config.project_root first). For project-defined tasks, prefer task.config_root so monorepo subproject tasks stay stable across cwds. Extend the e2e test to cover a global task invoked from both the monorepo root and a subproject directory.
|
Greptile is right — preferring `task.config_root` unconditionally regressed `MISE_PROJECT_ROOT` for tasks defined in the global config (`~/.config/mise/config.toml`), where `task.config_root` is the global config dir rather than the project the user is running the task from. Fix in 08e09f6: only flip the precedence when the task is from a project config. For global/system-config tasks the original `config.project_root → task.config_root` order is preserved. Extended the e2e test to invoke a global task from both the monorepo root and a subproject and assert `MISE_PROJECT_ROOT` is the local project root in each case. This comment was generated by an AI coding assistant. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 08e09f6. Configure here.
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.1 x -- echo |
19.3 ± 0.7 | 17.9 | 24.4 | 1.00 |
mise x -- echo |
19.6 ± 1.0 | 18.1 | 23.7 | 1.02 ± 0.06 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.1 env |
19.5 ± 1.0 | 17.7 | 25.8 | 1.00 |
mise env |
19.9 ± 1.0 | 17.6 | 24.9 | 1.02 ± 0.07 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.1 hook-env |
20.4 ± 1.0 | 18.6 | 25.7 | 1.03 ± 0.06 |
mise hook-env |
19.7 ± 0.6 | 18.4 | 22.3 | 1.00 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.1 ls |
16.7 ± 1.0 | 14.8 | 20.2 | 1.00 |
mise ls |
18.1 ± 1.0 | 15.6 | 22.5 | 1.08 ± 0.09 |
xtasks/test/perf
| Command | mise-2026.5.1 | mise | Variance |
|---|---|---|---|
| install (cached) | 118ms | 120ms | -1% |
| ls (cached) | 57ms | 57ms | +0% |
| bin-paths (cached) | 65ms | 65ms | +0% |
| task-ls (cached) | 477ms | 476ms | +0% |
is_global_config(&task.config_source) only matches global *TOML* config files. For tasks loaded from a script in ~/.config/mise/tasks/ (or remote tasks), config_source is the script path / remote URL and the guard misfires — those tasks fell through to the new branch and silently received the global config dir as MISE_PROJECT_ROOT. Use task.global || task.is_remote() instead, which mark_tasks_as_global already sets for both inline and file-based global tasks. Extend the e2e test with a script-based global task to cover the regression.
|
Greptile, Cursor Bugbot and Gemini all converged on the same gap: `is_global_config(&task.config_source)` only matches global TOML config paths, so global file tasks under `~/.config/mise/tasks/` (whose `config_source` is the script's own path) fell through to the new branch and would have received the global config dir as `MISE_PROJECT_ROOT`. Switched the guard to `task.global || task.is_remote()` in ea7f632, which `mark_tasks_as_global` already sets for both inline TOML and file-based global tasks (and `is_remote()` covers `git::`/`http(s)://` includes that Gemini flagged). Extended the e2e test with a script-based global task that's invoked from both the monorepo root and the subproject; both assert the local project root. This comment was generated by an AI coding assistant. |
### 🚀 Features - **(aqua)** support registry libc variants by @jdx in [#9652](#9652) - **(bin-paths)** add executable names output by @risu729 in [#9617](#9617) ### 🐛 Bug Fixes - **(aqua)** preserve configured file extensions by @risu729 in [#9611](#9611) - **(aqua)** support registry file links by @risu729 in [#9610](#9610) - **(backend)** reject bare package backend names by @risu729 in [#9608](#9608) - **(backend)** apply inline tool option overrides by @risu729 in [#9306](#9306) - **(backend)** skip versions host for local tool opts by @risu729 in [#9568](#9568) - **(github)** chmod explicit archive bin by @risu729 in [#9609](#9609) - **(install)** skip remote-versions refresh in prefer-offline mode by @jdx in [#9627](#9627) - **(lock)** scope targets to active project root by @risu729 in [#9319](#9319) - **(lockfile)** respect existing platforms during auto-lock by @jdx in [#9621](#9621) - **(pipx)** filter yanked pypi releases by @risu729 in [#9607](#9607) - **(pipx)** declare python as a backend dependency by @jdx in [#9678](#9678) - **(schema)** update refs to $defs in mise-registry-tool.json by @risu729 in [#9671](#9671) - **(task)** terminate parallel siblings on failure via process groups by @jdx in [#9655](#9655) - **(task)** stable MISE_PROJECT_ROOT for monorepo tasks, add MISE_MONOREPO_ROOT by @jdx in [#9657](#9657) - **(trust)** run enter hooks after trusting config by @risu729 in [#9634](#9634) - **(ui)** stop clearing screen for prompts by @jdx in [#9619](#9619) - use /bin/cp on macos by @pdehlke in [#9656](#9656) ### 🚜 Refactor - **(aqua)** store aqua var defaults as strings by @risu729 in [#9645](#9645) - **(config)** support structured TOML values in registry backend options by @risu729 in [#9584](#9584) - **(deps)** remove serde_derive dependency by @risu729 in [#9670](#9670) - **(deps)** remove anyhow dependency by @risu729 in [#9661](#9661) - **(deps)** use std::sync::LazyLock instead of once_cell::Lazy by @risu729 in [#9668](#9668) - **(schema)** generate task schema from mise schema by @risu729 in [#9581](#9581) - **(schema)** reuse task props with unevaluatedProperties by @risu729 in [#9582](#9582) - **(schema)** reuse registry common types by @risu729 in [#9648](#9648) - **(schema)** reuse plugin script config by @risu729 in [#9647](#9647) - **(schema)** use $defs in schema files by @risu729 in [#9646](#9646) ### 📚 Documentation - **(node)** add tips for enabling node idiomatic by @fu050409 in [#9675](#9675) ### 🧪 Testing - **(cli)** remove nondeterministic tool depends assertion by @risu729 in [#9633](#9633) - **(e2e)** pin uv to 0.11.8 around astral-sh/uv#19278 by @jdx in [#9618](#9618) - **(e2e)** wait for docker env cleanup by @risu729 in [#9631](#9631) - **(zig)** use official zig instead of mach mirror by @jdx in [#9659](#9659) ### 📦️ Dependency Updates - fall through to hash check when providers have no outputs by @jdx in [#9622](#9622) - bump Cargo.lock by @jdx in [#9625](#9625) ### 📦 Registry - remove registry depends by @risu729 in [#9571](#9571) - add code-review-graph (pipx:code-review-graph) by @chautruonglong in [#9673](#9673) ### Chore - **(ci)** split large registry test-tool changes by @risu729 in [#9628](#9628) - **(ci)** make perf script robust to runner noise by @jdx in [#9635](#9635) - **(ci)** skip hyperfine comments without permission by @risu729 in [#9629](#9629) ### New Contributors - @chautruonglong made their first contribution in [#9673](#9673) - @pdehlke made their first contribution in [#9656](#9656) ## 📦 Aqua Registry Updates ### New Packages (5) - [`anthropics/anthropic-cli`](https://github.com/anthropics/anthropic-cli) - [`crates.io/wasmi_cli`](https://github.com/wasmi-labs/wasmi) - [`openclaw/gogcli`](https://github.com/openclaw/gogcli) - `racket-lang.org/racket-minimal` - [`runs-on/cli`](https://github.com/runs-on/cli) ### Updated Packages (13) - [`UpCloudLtd/upcloud-cli`](https://github.com/UpCloudLtd/upcloud-cli) - [`aristocratos/btop`](https://github.com/aristocratos/btop) - [`dprint/dprint`](https://github.com/dprint/dprint) - [`j178/prek`](https://github.com/j178/prek) - [`jdx/hk`](https://github.com/jdx/hk) - [`jdx/mise`](https://github.com/jdx/mise) - [`jdx/usage`](https://github.com/jdx/usage) - [`jreleaser/jreleaser`](https://github.com/jreleaser/jreleaser) - [`jreleaser/jreleaser/standalone`](https://github.com/jreleaser/jreleaser) - [`pnpm/pnpm`](https://github.com/pnpm/pnpm) - [`suzuki-shunsuke/cmdx`](https://github.com/suzuki-shunsuke/cmdx) - [`suzuki-shunsuke/ghir`](https://github.com/suzuki-shunsuke/ghir) - [`twpayne/chezmoi`](https://github.com/twpayne/chezmoi)

Summary
MISE_PROJECT_ROOTwas cwd-dependent: from inside the subproject it pointed at the subproject's directory, but from the monorepo root or an intermediate directory it pointed at the monorepo root. The value was sourced fromconfig.project_root(which is the closest project to cwd) before falling back to the task's ownconfig_root.config_rootsoMISE_PROJECT_ROOTis always the directory of the mise.toml that defined the task — stable regardless of where the task was invoked from. For non-monorepo configstask.config_root == config.project_root, so behavior is unchanged.MISE_MONOREPO_ROOT(the directory of the config withexperimental_monorepo_root = true) so subproject tasks can reference the monorepo root without a cwd-dependent fallback.Refs #9639.
Test plan
e2e/tasks/test_task_monorepo_project_rootexercises a subproject task invoked from the monorepo root, an intermediate dir, and the subproject dir, and assertsMISE_PROJECT_ROOTandMISE_MONOREPO_ROOTboth stay stable.test_task_monorepo_config_context,test_task_monorepo_run_project_tasks,test_task_monorepo_run_project_local_tasks,test_task_monorepo_global_tasks).This PR was generated by an AI coding assistant.
Note
Medium Risk
Changes task execution environment variable semantics for
MISE_PROJECT_ROOT(especially in monorepos) and adds a newMISE_MONOREPO_ROOT, which could affect scripts relying on prior cwd-dependent behavior despite added e2e coverage.Overview
Makes
MISE_PROJECT_ROOTresolve to the task’s definingmise.tomldirectory (viatask.config_root) so monorepo subproject tasks get a stable project root regardless of invocation cwd, while preserving prior behavior for global and remote tasks.Adds
MISE_MONOREPO_ROOTto the task environment (derived from the config withexperimental_monorepo_root = true), updates task docs accordingly, and introduces a new e2e test covering monorepo/subproject/root/global task cases.Reviewed by Cursor Bugbot for commit ea7f632. Bugbot is set up for automated code reviews on this repo. Configure here.