Skip to content

fix(task): stable MISE_PROJECT_ROOT for monorepo tasks, add MISE_MONOREPO_ROOT#9657

Merged
jdx merged 3 commits intomainfrom
fix/monorepo-project-root
May 6, 2026
Merged

fix(task): stable MISE_PROJECT_ROOT for monorepo tasks, add MISE_MONOREPO_ROOT#9657
jdx merged 3 commits intomainfrom
fix/monorepo-project-root

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented May 6, 2026

Summary

  • For monorepo tasks, MISE_PROJECT_ROOT was 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 from config.project_root (which is the closest project to cwd) before falling back to the task's own config_root.
  • Prefer the task's own config_root so MISE_PROJECT_ROOT is always the directory of the mise.toml that defined the task — stable regardless of where the task was invoked from. For non-monorepo configs task.config_root == config.project_root, so behavior is unchanged.
  • Add MISE_MONOREPO_ROOT (the directory of the config with experimental_monorepo_root = true) so subproject tasks can reference the monorepo root without a cwd-dependent fallback.

Refs #9639.

Test plan

  • New e2e test e2e/tasks/test_task_monorepo_project_root exercises a subproject task invoked from the monorepo root, an intermediate dir, and the subproject dir, and asserts MISE_PROJECT_ROOT and MISE_MONOREPO_ROOT both stay stable.
  • Existing monorepo task tests still pass (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 new MISE_MONOREPO_ROOT, which could affect scripts relying on prior cwd-dependent behavior despite added e2e coverage.

Overview
Makes MISE_PROJECT_ROOT resolve to the task’s defining mise.toml directory (via task.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_ROOT to the task environment (derived from the config with experimental_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.

…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
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 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.

Comment thread src/task/task_executor.rs Outdated
Comment on lines 285 to 287
if let Some(root) = task.config_root.clone().or(config.project_root.clone()) {
env.insert("MISE_PROJECT_ROOT".into(), root.display().to_string());
}
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

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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 6, 2026

Greptile Summary

This PR fixes MISE_PROJECT_ROOT stability in monorepo subproject tasks by preferring task.config_root (the directory of the defining mise.toml) over the cwd-derived config.project_root, and adds MISE_MONOREPO_ROOT so subproject tasks can always locate the monorepo root without relying on cwd.

  • MISE_PROJECT_ROOT precedence change: for non-global, non-remote tasks task.config_root is now preferred; for global tasks (both inline and script-file, both correctly detected via task.global) and remote tasks, config.project_root is still preferred, preserving the original behaviour.
  • New MISE_MONOREPO_ROOT env var: sourced from the config with experimental_monorepo_root = true; only injected when experimental mode is active, matching existing monorepo-feature gating.
  • E2e test: covers all six invocation scenarios (3 working directories × subproject task, plus root task, global inline task, and global file task) with explicit regression coverage for the global-task path.

Confidence Score: 5/5

Safe 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

Comment thread src/task/task_executor.rs Outdated
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.
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented May 6, 2026

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.

Comment thread src/task/task_executor.rs Outdated
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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread src/task/task_executor.rs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Hyperfine Performance

mise x -- echo

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.
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented May 6, 2026

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.

@jdx jdx merged commit 34f6cb7 into main May 6, 2026
35 of 37 checks passed
@jdx jdx deleted the fix/monorepo-project-root branch May 6, 2026 22:38
mise-en-dev added a commit that referenced this pull request May 7, 2026
### 🚀 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)
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.

1 participant