Skip to content

fix(prepare): resolve sources/outputs relative to dir when set#8472

Merged
jdx merged 1 commit intomainfrom
fix/prepare-dir-source-resolution
Mar 5, 2026
Merged

fix(prepare): resolve sources/outputs relative to dir when set#8472
jdx merged 1 commit intomainfrom
fix/prepare-dir-source-resolution

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 5, 2026

Summary

  • When a prepare provider sets dir, relative source and output paths now resolve against project_root/dir instead of just project_root
  • Adds config_root() helper to ProviderBase that returns the effective root directory
  • Updates all built-in providers (npm, yarn, pnpm, bun, go, pip, poetry, uv, bundler, composer, git-submodule) to use config_root() for sources, outputs, cwd, and applicability checks
  • Adds e2e test verifying dir affects source/output resolution and freshness tracking

Closes #8471

Test plan

  • mise run build — compiles successfully
  • mise run test:e2e test_prepare — all existing + new tests pass
  • Manual: create a project with dir = "subdir" and relative sources, verify prepare-state.toml includes hashed sources

🤖 Generated with Claude Code


Note

Medium Risk
Changes path resolution and working directories for all built-in prepare providers and custom glob expansion, which can affect applicability detection and freshness/state tracking in existing projects (especially monorepos/subdirs).

Overview
Prepare providers now resolve relative sources/outputs (and built-in provider lockfile checks) against an effective root derived from dir (project_root/dir) instead of always using project_root.

This introduces ProviderBase::config_root() and updates all built-in providers (npm/yarn/pnpm/bun/go/pip/poetry/uv/bundler/composer/git-submodule) plus custom provider glob expansion to use it for sources, outputs, cwd, and applicability checks, with an added e2e test ensuring dir affects listing and freshness hashing for a custom provider.

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

When a prepare provider sets `dir`, relative source and output paths now
resolve against `project_root/dir` instead of just `project_root`. This
fixes change detection for custom providers with `dir` set, where source
hashes were never saved to `prepare-state.toml` because the files
couldn't be found at the project root.

Adds `config_root()` helper to `ProviderBase` and updates all built-in
providers to use it consistently for sources, outputs, cwd, and
applicability checks.

Closes #8471

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@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 addresses an issue where prepare providers were not correctly resolving relative paths for sources and outputs when a dir option was set in their configuration. The change introduces a unified way to determine the effective root directory for these operations, ensuring that all built-in providers respect the dir setting. This enhances the flexibility and correctness of how mise handles project-specific build and dependency management within subdirectories.

Highlights

  • Path Resolution Fix: Relative source and output paths for prepare providers now correctly resolve against project_root/dir when the dir option is specified in the configuration, instead of always using project_root.
  • New Helper Function: A new config_root() helper method has been added to ProviderBase to consistently return the effective root directory for path resolution, accounting for the dir configuration.
  • Provider Updates: All built-in prepare providers (npm, yarn, pnpm, bun, go, pip, poetry, uv, bundler, composer, git-submodule) have been updated to utilize the new config_root() method for determining sources, outputs, current working directory (cwd), and applicability checks.
  • E2E Test Coverage: A new end-to-end test has been added to verify that the dir option correctly influences source/output path resolution and freshness tracking for custom prepare providers.
Changelog
  • e2e/cli/test_prepare
    • Added a new e2e test case to validate that the dir option correctly influences source/output resolution and freshness tracking for custom prepare providers.
  • src/prepare/providers/bun.rs
    • Updated lockfile_path, sources, outputs, and prepare_command to use self.base.config_root() for path resolution.
  • src/prepare/providers/bundler.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/composer.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/custom.rs
    • Modified paths_from_patterns to resolve relative patterns against self.base.config_root().
  • src/prepare/providers/git_submodule.rs
    • Updated submodule_paths, sources, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/go.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/mod.rs
    • Added a new public method config_root() to ProviderBase to return the effective root directory based on the dir configuration.
  • src/prepare/providers/npm.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/pip.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/pnpm.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/poetry.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/uv.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
  • src/prepare/providers/yarn.rs
    • Updated sources, outputs, prepare_command, and is_applicable to use self.base.config_root() for path resolution.
Activity
  • The author has confirmed that mise run build compiles successfully.
  • The author has confirmed that mise run test:e2e test_prepare passes all existing and new tests.
  • The pull request was generated with Claude Code, indicating AI assistance in its creation.
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.

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 correctly implements support for the dir option in prepare providers by introducing a config_root() helper. However, it highlights a pre-existing path traversal vulnerability in the git-submodule provider. The git-submodule provider parses the untrusted .gitmodules file and uses path values to construct file paths without proper validation, which could allow mise to interact with arbitrary files on the user's system. Additionally, my review includes suggestions to address minor performance issues where config_root() is called inside a loop and to reduce code duplication in path construction.

value
.strip_prefix('=')
.map(|value| self.base.project_root.join(value.trim()))
.map(|value| self.base.config_root().join(value.trim()))
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.

security-medium medium

This code block in submodule_paths is vulnerable to path traversal. The path values from the untrusted .gitmodules file are joined directly with the configuration root without validating that they are relative paths. This allows an attacker to provide malicious paths (e.g., absolute paths or parent directory references like path = /etc/passwd or path = ../../../etc/passwd), which could cause mise to interact with arbitrary files on the user's system, leading to information leakage or modification of file metadata. To remediate this, validate that the path values extracted from .gitmodules are relative and do not traverse outside the intended directory. You can use Path::is_relative() and ensure the resulting path is contained within the config_root. Additionally, for performance, self.base.config_root() is called repeatedly inside this loop; consider calling it once and reusing the result.

Comment on lines 28 to +29
for pattern in patterns {
let base_dir = self.base.config_root();
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 a minor performance improvement, base_dir can be initialized outside of the loop since it doesn't change between iterations.

Suggested change
for pattern in patterns {
let base_dir = self.base.config_root();
let base_dir = self.base.config_root();
for pattern in patterns {


// Use `go mod vendor` if vendor/ exists, otherwise `go mod download`
let vendor = self.base.project_root.join("vendor");
let vendor = self.base.config_root().join("vendor");
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

The path to the vendor directory is constructed here and also in the outputs function on line 37. To avoid this duplication and improve maintainability, you could consider creating a private helper method on GoPrepareProvider like fn vendor_path(&self) -> PathBuf that returns self.base.config_root().join("vendor"). Then, both outputs and prepare_command can call this helper.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR fixes a bug where sources and outputs in prepare providers were resolved relative to project_root instead of project_root/dir when a dir option was set. A new config_root() helper is added to ProviderBase and all 11 built-in providers are updated to use it consistently.

Key changes:

  • ProviderBase::config_root() returns project_root.join(dir) when dir is set, otherwise project_root — a clean, single-responsibility helper
  • All built-in providers updated to use config_root() for sources(), outputs(), is_applicable(), and cwd in default prepare_command()
  • Custom providers' expand_globs() updated to use config_root() for relative path resolution
  • All providers that accept a user-supplied run override correctly pass project_root to PrepareCommand::from_string, which already handles dir internally via config.dir.as_ref().map(|d| project_root.join(d))
  • E2e test added for the custom provider with dir set, covering initial run, freshness on re-run, and staleness detection after source modification

Confidence Score: 5/5

  • This PR is safe to merge — the changes are a consistent, mechanical fix with no logic risk.
  • All 11 providers are updated uniformly. The config_root() helper is trivially correct. The interaction with PrepareCommand::from_string (which already handles dir internally) is consistent and avoids double-application of the dir offset. The e2e test verifies the core behavioral fix end-to-end.
  • No files require special attention.

Important Files Changed

Filename Overview
src/prepare/providers/mod.rs Adds config_root() helper to ProviderBase that returns project_root/dir when dir is set, otherwise project_root. Clean, well-placed abstraction with no issues.
src/prepare/providers/custom.rs Uses config_root() in expand_globs for relative path resolution. prepare_command correctly passes project_root to from_string, which internally handles dir for cwd via `config.dir.as_ref().map(
src/prepare/providers/git_submodule.rs Correctly uses config_root() for .gitmodules path, submodule path resolution, sources, and is_applicable. Custom run override correctly passes project_root to from_string which handles dir internally.
e2e/cli/test_prepare Adds e2e test for dir option in custom providers. Tests source resolution, freshness on first/second run, and staleness detection on file modification. Test assertions use substring matching which is reliable.
src/prepare/providers/npm.rs Mechanically replaces project_root with config_root() for sources, outputs, cwd, and is_applicable. Clean and correct.
src/prepare/providers/go.rs Correctly updates all path resolutions to use config_root(). Note that config_root() is called multiple times in prepare_command (3 times), creating redundant PathBuf allocations — minor style point.
src/prepare/providers/bun.rs Correctly updates lockfile_path, sources, outputs, and cwd to use config_root(). All changes are consistent and accurate.
src/prepare/providers/pip.rs Sources, outputs, cwd, and is_applicable all updated to config_root(). The requirements.txt arg to pip remains a relative string, which resolves correctly against the updated cwd.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PrepareProvider] --> B{config.dir set?}
    B -->|Yes| C["config_root() = project_root / dir"]
    B -->|No| D["config_root() = project_root"]
    C --> E[sources / outputs / is_applicable / cwd]
    D --> E
    A --> F{Custom run override?}
    F -->|Yes| G["PrepareCommand::from_string(run, project_root, config)"]
    G --> H{"config.dir set?"}
    H -->|Yes| I["cwd = project_root / dir"]
    H -->|No| J["cwd = project_root"]
    F -->|No| K["Default PrepareCommand with cwd = config_root()"]
Loading

Last reviewed commit: 57198ee

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 5, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 x -- echo 26.0 ± 0.5 25.3 31.0 1.00
mise x -- echo 26.2 ± 0.6 25.4 31.5 1.01 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 env 25.5 ± 0.5 24.6 28.3 1.00
mise env 25.6 ± 0.5 24.7 27.2 1.01 ± 0.03

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 hook-env 26.1 ± 0.4 25.5 29.9 1.00
mise hook-env 26.2 ± 0.3 25.4 28.1 1.00 ± 0.02

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 ls 25.2 ± 0.3 24.6 27.4 1.00
mise ls 25.4 ± 0.4 24.8 29.5 1.01 ± 0.02

xtasks/test/perf

Command mise-2026.3.3 mise Variance
install (cached) 161ms 160ms +0%
ls (cached) 88ms 86ms +2%
bin-paths (cached) 90ms 90ms +0%
task-ls (cached) 822ms 818ms +0%

@jdx jdx merged commit 07d96da into main Mar 5, 2026
38 checks passed
@jdx jdx deleted the fix/prepare-dir-source-resolution branch March 5, 2026 12:23
mise-en-dev added a commit that referenced this pull request Mar 7, 2026
### 🚀 Features

- **(github)** keep exe extensions on Windows by @iki in
[#8424](#8424)
- **(task)** add `interactive` field for exclusive terminal access by
@jdx in [#8491](#8491)
- add header comment to generated lockfiles by @ivy in
[#8481](#8481)
- runtime musl/glibc detection for correct libc variant selection by
@jdx in [#8490](#8490)

### 🐛 Bug Fixes

- **(github)** use registry platform options during install by @jdx in
[#8492](#8492)
- **(http)** store tool opts as native TOML to fix platform switching by
@jdx in [#8448](#8448)
- **(installer)** error if MISE_INSTALL_PATH is a directory by @jdx in
[#8468](#8468)
- **(prepare)** resolve sources/outputs relative to `dir` when set by
@jdx in [#8472](#8472)
- **(ruby)** fetch precompiled binary by release tag instead of listing
all releases by @jdx in [#8488](#8488)
- **(schema)** support structured objects in task depends by @risu729 in
[#8463](#8463)
- **(task)** replace println!/eprintln! with calm_io in task output
macros by @vmaleze in [#8485](#8485)
- handle scoped npm package names without backend prefix by @jdx in
[#8477](#8477)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:copr docker digest to c485c4c by
@renovate[bot] in [#8484](#8484)
- update ghcr.io/jdx/mise:alpine docker digest to 8118bc7 by
@renovate[bot] in [#8483](#8483)

### 📦 Registry

- disable sd version test by @jdx in
[#8489](#8489)

### New Contributors

- @ivy made their first contribution in
[#8481](#8481)
- @iki made their first contribution in
[#8424](#8424)

## 📦 Aqua Registry Updates

#### New Packages (5)

- [`datadog-labs/pup`](https://github.com/datadog-labs/pup)
- [`k1LoW/mo`](https://github.com/k1LoW/mo)
- [`rtk-ai/rtk`](https://github.com/rtk-ai/rtk)
-
[`suzuki-shunsuke/docfresh`](https://github.com/suzuki-shunsuke/docfresh)
- [`yashikota/exiftool-go`](https://github.com/yashikota/exiftool-go)

#### Updated Packages (6)

- [`cloudflare/cloudflared`](https://github.com/cloudflare/cloudflared)
- [`mozilla/sccache`](https://github.com/mozilla/sccache)
- [`owenlamont/ryl`](https://github.com/owenlamont/ryl)
- [`spinel-coop/rv`](https://github.com/spinel-coop/rv)
-
[`technicalpickles/envsense`](https://github.com/technicalpickles/envsense)
- [`weaviate/weaviate`](https://github.com/weaviate/weaviate)
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