Skip to content

fix(config): resolve relative path: tool versions against config root#9320

Merged
jdx merged 1 commit intomainfrom
claude/gifted-diffie-0c6502
Apr 23, 2026
Merged

fix(config): resolve relative path: tool versions against config root#9320
jdx merged 1 commit intomainfrom
claude/gifted-diffie-0c6502

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 23, 2026

Summary

Fixes #9288.

path: values in [tools] were stored as-is, then later joined with the tool's installs_path when resolving the install directory. For absolute paths this works because Path::join replaces when given an absolute path, but for relative paths like path:./packages/logr it produced garbage:

~/.local/share/mise/installs/logr/./packages/logr

Now ToolRequest::new resolves path: at parse time:

  • absolute paths pass through unchanged
  • ~/ expands to $HOME
  • a leading ./ is stripped
  • any remaining relative path is joined with config_root(source) when the source is a config file, or CWD for CLI args

ToolRequest::version() now renders the stored PathBuf via display_user() so ~/ substitution is preserved in mise ls output and lockfiles.

Test plan

  • New e2e test e2e/cli/test_tool_version_path covering path:./x, bare relative path:x, path:~/x, and absolute paths
  • Existing e2e/cli/test_use path:~/workdir/mydummy assertion still passes
  • cargo check and mise run lint-fix clean
  • Unit tests in toolset::tool_request pass

🤖 Generated with Claude Code


Note

Medium Risk
Changes how path: tool versions are parsed and displayed, which can affect tool resolution, lockfile entries, and mise where/ls output for existing configs using relative paths.

Overview
Fixes path: tool versions so relative paths are resolved at parse time against the config file’s root (or CWD for CLI usage), with ~/ expansion and ./ stripping, preventing bogus install-path joins.

Updates ToolRequest::version() to render path: values via display_user() (preserving ~/ in output/lockfiles) and adds an e2e test covering relative, ~/, and absolute path: cases.

Reviewed by Cursor Bugbot for commit 27e0c90. Bugbot is set up for automated code reviews on this repo. Configure here.

Previously, `path:./packages/foo` in `[tools]` was stored as-is and later
joined with the tool's installs_path, producing nonsense like
`~/.local/share/mise/installs/foo/./packages/foo`. Resolve the value at
parse time: expand `~/`, strip `./`, and anchor remaining relative paths
to the config file's directory (or CWD for CLI args).

Closes #9288

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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 implements path resolution for path: tool versions, supporting relative paths, ./ prefixes, and ~/ expansion, along with a new E2E test. The review feedback highlights a potential issue where resolving paths to absolute values at parse time could break lockfile portability across different machines. There is also a suggestion to improve the robustness of home directory expansion by handling the bare ~ prefix.

Comment on lines +86 to +94
Some(("path", p)) => {
let path = resolve_path(p, &source);
Self::Path {
path,
options: backend.opts(),
backend,
source,
}
}
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

Resolving the path to an absolute path at parse time in ToolRequest::new breaks the portability of lockfiles. When mise lock is run, the resulting lockfile will contain an absolute path (or a ~/ prefixed path) that is specific to the current user's environment. If another user clones the repository to a different location, the path: tool will fail to resolve for them because the stored path is no longer relative to the project root.

To maintain portability while fixing the "garbage path" issue, consider storing the path as provided (relative) in the ToolRequest struct and resolving it to an absolute path only when the actual filesystem path is required (e.g., in install_path() or during ToolVersion resolution).

Comment on lines +392 to +394
if let Ok(rest) = p.strip_prefix("~/") {
return dirs::HOME.join(rest);
}
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

Using strip_prefix("~/") will not match a bare ~ (e.g., path:~). It is more robust to strip the ~ component, which handles both ~ and ~/ correctly as path components.

Suggested change
if let Ok(rest) = p.strip_prefix("~/") {
return dirs::HOME.join(rest);
}
if let Ok(rest) = p.strip_prefix("~") {
return dirs::HOME.join(rest);
}

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

This PR fixes a bug where relative path: tool version values were stored verbatim and later incorrectly joined with the backend's installs_path, producing garbage paths like ~/.local/share/mise/installs/logr/./packages/logr. The fix resolves path: values at parse time in ToolRequest::new via a new resolve_path helper, and updates version() to render the stored PathBuf using display_user() so the ~/ substitution is preserved in mise ls output and lockfiles.

Confidence Score: 5/5

Safe to merge — fixes a real path-resolution bug with no introduced regressions or new failure modes.

All changed logic is correct: paths are always resolved to absolute values at parse time, display_user() roundtrips cleanly through lockfile re-parsing via the ~/ strip_prefix branch, and the e2e test covers all four documented input forms. No P0/P1 findings remain.

e2e/cli/test_tool_version_path — created non-executable (mode 100644); verify the test runner invokes it via bash rather than direct execution.

Important Files Changed

Filename Overview
src/toolset/tool_request.rs Adds resolve_path helper that resolves path: values to absolute paths at parse time; updates version() to use display_user() for ~/-friendly output. Logic is correct for all documented cases.
e2e/cli/test_tool_version_path New e2e test covering ./-relative, bare-relative, ~/, and absolute path: variants. Created with mode 100644 (non-executable); correctness depends on test framework's working directory being $HOME/workdir.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["ToolRequest::new(backend, s, source)"] --> B{"s starts with 'path:'"}
    B -- No --> C[Other variant: Version / Ref / Prefix / Sub / System]
    B -- Yes --> D["resolve_path(p, &source)"]
    D --> E{"starts with ~/"}
    E -- Yes --> F["dirs::HOME.join(rest)"]
    E -- No --> G{"is_absolute"}
    G -- Yes --> H[pass through as-is]
    G -- No --> I["strip leading ./"]
    I --> J{"source.path() is Some"}
    J -- Yes --> K["config_root(src).join(p)"]
    J -- No --> L["dirs::CWD.join(p)"]
    F & H & K & L --> M["Self::Path { path: absolute_path }"]
    M --> N["version() calls path.display_user()"]
    N --> O["/home/user/... rendered as ~/..."]
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(config): resolve relative path: tool..." | Re-trigger Greptile

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
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.

P2 Test file is non-executable (mode 100644)

The file is created with 100644 permissions. If the e2e runner discovers tests via glob and executes them directly (e.g., ./test_tool_version_path) rather than via bash test_tool_version_path, the script will fail with a permission-denied error. Other tests in e2e/cli/ that already pass should have mode 100755 — consider matching that convention here.

(No code change needed — just chmod +x e2e/cli/test_tool_version_path or set the git mode with git update-index --chmod=+x.)

Fix in Claude Code

@jdx jdx merged commit c8f1514 into main Apr 23, 2026
38 checks passed
@jdx jdx deleted the claude/gifted-diffie-0c6502 branch April 23, 2026 14:10
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.19 x -- echo 24.7 ± 3.5 22.9 57.0 1.00 ± 0.14
mise x -- echo 24.7 ± 0.7 23.4 30.6 1.00

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.19 env 23.5 ± 0.6 22.6 27.9 1.00
mise env 24.1 ± 0.6 22.9 25.6 1.03 ± 0.03

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.19 hook-env 24.6 ± 1.0 23.3 39.7 1.00
mise hook-env 25.2 ± 1.0 23.7 37.3 1.02 ± 0.06

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.19 ls 21.9 ± 0.4 21.0 24.7 1.00
mise ls 22.5 ± 0.4 21.4 24.4 1.03 ± 0.03

xtasks/test/perf

Command mise-2026.4.19 mise Variance
install (cached) 169ms 174ms -2%
ls (cached) 79ms 82ms -3%
bin-paths (cached) 84ms 86ms -2%
task-ls (cached) 804ms 796ms +1%

mise-en-dev added a commit that referenced this pull request Apr 24, 2026
### 🐛 Bug Fixes

- **(config)** resolve relative path: tool versions against config root
by @jdx in [#9320](#9320)
- **(lock)** resolve @latest and prune poisoned lockfile entries by @jdx
in [#9321](#9321)
- fix - be able to work with regex in attestation check by @monotek in
[#9327](#9327)

### 🚜 Refactor

- **(aqua)** bake aqua registry from merged yaml by @risu729 in
[#9043](#9043)

### 📚 Documentation

- add cross-site announcement banner by @jdx in
[#9326](#9326)
- keep banner height in sync via ResizeObserver by @jdx in
[#9330](#9330)
- respect banner expires field by @jdx in
[#9334](#9334)

### 📦️ Dependency Updates

- bump communique to 1.0.2 by @jdx in
[#9313](#9313)
- bump communique to 1.0.3 by @jdx in
[#9332](#9332)
- update actions/setup-node digest to 48b55a0 by @renovate[bot] in
[#9339](#9339)
- update ghcr.io/jdx/mise:alpine docker digest to a92efa5 by
@renovate[bot] in [#9340](#9340)
- update ghcr.io/jdx/mise:rpm docker digest to 5c24f69 by @renovate[bot]
in [#9343](#9343)
- update rust docker digest to e4f09e8 by @renovate[bot] in
[#9345](#9345)
- update rui314/setup-mold digest to 9c9c13b by @renovate[bot] in
[#9344](#9344)
- update ghcr.io/jdx/mise:deb docker digest to a3afe3e by @renovate[bot]
in [#9342](#9342)
- update ghcr.io/jdx/mise:copr docker digest to 4098d5a by
@renovate[bot] in [#9341](#9341)
- update taiki-e/install-action digest to 74e87cb by @renovate[bot] in
[#9346](#9346)

### Chore

- **(ci)** remove cargo-vendor install from ppa publish by @jdx in
[#9312](#9312)
- **(release)** publish snap to stable channel by @jdx in
[#9318](#9318)
- remove FUNDING.yml in favor of jdx/.github default by @jdx in
[#9331](#9331)

## 📦 Aqua Registry

Updated [aqua-registry](https://github.com/aquaproj/aqua-registry):
[v4.492.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.492.0)
->
[v4.498.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.498.0).

Included aqua-registry releases:

-
[v4.493.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.493.0)
-
[v4.494.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.494.0)
-
[v4.494.1](https://github.com/aquaproj/aqua-registry/releases/tag/v4.494.1)
-
[v4.495.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.495.0)
-
[v4.496.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.496.0)
-
[v4.497.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.497.0)
-
[v4.498.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.498.0)
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