Skip to content

fix(install): don't cache nonexistent install paths#9553

Merged
jdx merged 1 commit intomainfrom
claude/magical-ptolemy-e362bc
May 3, 2026
Merged

fix(install): don't cache nonexistent install paths#9553
jdx merged 1 commit intomainfrom
claude/magical-ptolemy-e362bc

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented May 3, 2026

Summary

Why

During dependency_env for the go: backend tool, mise resolved go's install_path before go was installed. At that point find_in_shared_installs() couldn't find go anywhere (the --system installs dir didn't exist yet) and returned the user-dir path — which install_path() then cached. After go installed at the system dir, callers like core go's exec_env got the stale user path back from the cache and exported it as GOROOT, breaking every go-backend install in the same run.

The cache key is (backend, version), so a freshly-built ToolVersion from dependency_toolset collided with the earlier (stale) entry. The minimal correct fix is to only cache when the resolved path is real — pre-install lookups recompute on each call (cheap, only matters during install workflows) until the tool actually lands somewhere concrete.

Test plan

  • New unit test install_path_does_not_cache_nonexistent_paths (src/toolset/tool_version.rs) verifies the cache stays empty for non-existent paths and populates after the dir exists.
  • New slow e2e e2e/cli/test_install_system_go_backend_slow reproduces the original scenario: go + go:github.com/jdx/go-example installed together with --system --jobs 1, asserts both end up in the system installs dir.
  • cargo test — all 819 unit tests pass.
  • mise run lint clean.
  • Existing test_install_system, test_install_system_readonly, test_shared_install_dirs still pass.
  • Manual reproduction of the discussion's repro now succeeds end-to-end.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core ToolVersion::install_path() path resolution/caching used broadly during installs, so mistakes could mis-route tool lookups; the change is small and covered by new unit/e2e regression tests.

Overview
Fixes a regression where mise install --system/--shared could cache an install path before a tool was actually installed, causing later lookups (notably Go GOROOT/GOPATH for go: backends) to use a stale user-dir path.

ToolVersion::install_path() now only writes to INSTALL_PATH_CACHE when the resolved path exists on disk, and adds a unit test plus a new slow e2e test that installs go and a go: backend tool in the same --system invocation and asserts both land in the system installs dir.

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

`ToolVersion::install_path()` cached the resolved path even when the
path didn't yet exist on disk. During `mise install --system`, the
dependency_env build for a `go:` backend tool computed go's install
path before go itself installed, causing `find_in_shared_installs()` to
return the user-dir path (no shared dir contained go yet). That path
got cached. Once go installed at the system dir, callers like core
go's `exec_env` still got the stale user path back, exported it as
`GOROOT`, and `go install ...` for the dependent tool failed with
"cannot find GOROOT directory".

Now the cache only stores paths that actually exist, so pre-install
lookups recompute on each call until the tool lands somewhere
concrete. Adds a unit test for the cache behavior and a slow e2e
covering the original repro (go + a go-backend tool installed together
with `--system`).

Fixes #9526

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 fixes a bug where install_path() would cache a path before it actually existed on disk, leading to stale environment variables (like GOROOT) when tools were installed into shared directories mid-run. The changes ensure that the INSTALL_PATH_CACHE is only populated if the resolved path exists. The PR also includes a new E2E regression test and a unit test to verify this behavior. I have no feedback to provide as there were no review comments.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 3, 2026

Greptile Summary

This PR fixes a cache-poisoning bug in ToolVersion::install_path() that caused mise install --system to fail when installing go and a go: backend tool together. The fix is minimal and correct: only cache the resolved install path when it actually exists on disk, so that pre-install calls don't populate the cache with a stale user-dir fallback that survives after the tool installs to a different location (e.g. the system installs dir).

Confidence Score: 5/5

Safe to merge — minimal, targeted fix with a well-scoped unit test and e2e regression test.

The change is a one-liner guard (if path.exists()) that directly addresses the described root cause. The surrounding logic is preserved: find_in_shared_installs already calls exists() internally, so the extra path.exists() at the cache-write site is cheap and compositionally correct. The unit test and slow e2e test together give strong confidence in the fix. No regressions are visible in the diff.

No files require special attention.

Important Files Changed

Filename Overview
src/toolset/tool_version.rs Core fix: install_path() now guards the INSTALL_PATH_CACHE.insert with path.exists(), preventing stale cache entries for not-yet-installed tools. New unit test verifies the cache stays empty for non-existent paths and is populated once the directory is created.
e2e/cli/test_install_system_go_backend_slow New slow e2e regression test: installs go + go:github.com/jdx/go-example together via --system --jobs 1, asserts both land in the system installs dir and not the user dir.

Sequence Diagram

sequenceDiagram
    participant Installer
    participant ToolVersion
    participant Cache as INSTALL_PATH_CACHE
    participant FS as Filesystem

    Note over Installer,FS: Before fix — cache poisoned pre-install
    Installer->>ToolVersion: install_path() [go not yet installed]
    ToolVersion->>FS: find_in_shared_installs → user-dir (missing)
    ToolVersion->>Cache: insert(stale user-dir path)  ❌
    Installer->>Installer: install go → system dir
    Installer->>ToolVersion: install_path() [go now in system dir]
    ToolVersion->>Cache: get() → stale user-dir path ❌

    Note over Installer,FS: After fix — only cache when path exists
    Installer->>ToolVersion: install_path() [go not yet installed]
    ToolVersion->>FS: find_in_shared_installs → user-dir (missing)
    ToolVersion->>FS: path.exists()? → false
    Note right of ToolVersion: skip cache insert ✓
    Installer->>Installer: install go → system dir
    Installer->>ToolVersion: install_path() [go now in system dir]
    ToolVersion->>FS: find_in_shared_installs → system dir (exists)
    ToolVersion->>FS: path.exists()? → true
    ToolVersion->>Cache: insert(system dir path) ✓
    Cache-->>ToolVersion: system dir path ✓
Loading

Reviews (1): Last reviewed commit: "fix(install): don't cache nonexistent in..." | Re-trigger Greptile

@jdx jdx enabled auto-merge (squash) May 3, 2026 00:30
@jdx jdx merged commit c567684 into main May 3, 2026
38 checks passed
@jdx jdx deleted the claude/magical-ptolemy-e362bc branch May 3, 2026 00:41
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 3, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 x -- echo 23.8 ± 0.4 23.2 28.5 1.00
mise x -- echo 24.5 ± 0.7 23.7 33.7 1.03 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 env 23.2 ± 0.5 22.5 29.1 1.00
mise env 23.9 ± 0.6 23.1 29.9 1.03 ± 0.03

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 hook-env 24.0 ± 0.3 23.2 26.1 1.00
mise hook-env 25.2 ± 0.6 23.9 27.0 1.05 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 ls 22.2 ± 0.9 20.6 31.7 1.00
mise ls 22.9 ± 0.5 21.2 24.2 1.03 ± 0.04

xtasks/test/perf

Command mise-2026.4.28 mise Variance
install (cached) 160ms 164ms -2%
ls (cached) 81ms 83ms -2%
bin-paths (cached) 87ms 88ms -1%
task-ls (cached) 805ms 795ms +1%

mise-en-dev added a commit that referenced this pull request May 3, 2026
### 🚀 Features

- **(conda)** graduate conda backend out of experimental by @jdx in
[#9544](#9544)
- **(deps)** Add dart and flutter providers by @tjarvstrand in
[#9505](#9505)
- **(registry)** add neo4j by @mnm364 in
[#9525](#9525)
- **(registry)** add rustfs by @mnm364 in
[#9530](#9530)
- **(task)** support exclusion patterns in task sources by
@jlarmstrongiv in [#9496](#9496)
- **(vfox)** add stat function to lua file module by @esteve in
[#9497](#9497)

### 🐛 Bug Fixes

- **(backend)** flag regex prerelease versions by @jdx in
[#9500](#9500)
- **(backend)** mark -nightly/-canary/-experimental as prereleases by
@jdx in [#9523](#9523)
- **(backend)** suppress no-versions warning for unresolved-latest
backends by @jdx in [#9548](#9548)
- **(backend)** include dotnet prereleases from package flags by @jdx in
[#9551](#9551)
- **(backend)** scope PEP 440 prerelease detection to Python backends by
@jdx in [#9558](#9558)
- **(cargo)** Apply install_env during cargo install by @c22 in
[#9502](#9502)
- **(copr)** drop epel-9 chroots since rust >= 1.91 is unavailable by
@jdx in [#9484](#9484)
- **(github)** skip attestations on non-default api_url by @jdx in
[#9486](#9486)
- **(github)** retry ip allow list errors without auth by @risu729 in
[#9506](#9506)
- **(http)** update versions host tracking endpoint by @jdx in
[#9527](#9527)
- **(install)** don't warn for configured tools when version is passed
via CLI by @jdx in [#9522](#9522)
- **(install)** refresh latest before installing missing tools by @jdx
in [#9545](#9545)
- **(install)** don't cache nonexistent install paths by @jdx in
[#9553](#9553)
- **(lockfile)** don't propagate ad-hoc CLI overrides into the project
lockfile by @jdx in [#9562](#9562)
- **(plugin)** detect plugin types after cloning by @risu729 in
[#9540](#9540)
- **(release)** pass --no-git-checks to aube publish by @jdx in
[#9483](#9483)
- **(task)** convert PATH to MSYS Unix form when spawning POSIX shells
on Windows by @JamBalaya56562 in
[#9547](#9547)

### 📚 Documentation

- **(contributing)** require popularity check for registry PRs by @jdx
in
[7bbeebe](7bbeebe)
- **(watch)** update pitchfork domain to en.dev by @risu729 in
[#9536](#9536)
- document ghtkn GitHub token setup by @jdx in
[#9546](#9546)
- clarify registry backend acceptance policy by @jdx in
[#9543](#9543)
- Change exec command to use bash for variable echo by @kuboon in
[#9567](#9567)

### 🧪 Testing

- **(e2e)** run test-tool targets in parallel by @jdx in
[#9564](#9564)
- **(e2e)** run tests in parallel by @jdx in
[#9563](#9563)
- **(e2e)** bind-mount /tmp on disk and surface failed tests in CI
summary by @jdx in [#9570](#9570)
- **(tasks)** migrate test_task_help atask to usage field by @jdx in
[#9549](#9549)

### 📦️ Dependency Updates

- update fedora:45 docker digest to 8b838b3 by @renovate[bot] in
[#9507](#9507)
- update ghcr.io/jdx/mise:deb docker digest to f02194c by @renovate[bot]
in [#9509](#9509)
- update taiki-e/install-action digest to 7769b73 by @renovate[bot] in
[#9512](#9512)
- update ghcr.io/jdx/mise:alpine docker digest to 581f8a8 by
@renovate[bot] in [#9508](#9508)
- update rust crate ctor to v0.10.1 by @renovate[bot] in
[#9515](#9515)
- update ghcr.io/jdx/mise:rpm docker digest to a5c9655 by @renovate[bot]
in [#9510](#9510)
- update rust docker digest to a9cfb75 by @renovate[bot] in
[#9511](#9511)
- update rust crate age to v0.11.3 by @renovate[bot] in
[#9514](#9514)
- update rust crate jiff to v0.2.24 by @renovate[bot] in
[#9516](#9516)
- update dependency vitepress-plugin-tabs to ^0.9.0 by @renovate[bot] in
[#9518](#9518)
- update autofix-ci/action action to v1.3.4 by @renovate[bot] in
[#9513](#9513)
- update rust crate usage-lib to v3.2.1 by @renovate[bot] in
[#9517](#9517)
- update apple-actions/import-codesign-certs action to v7 by
@renovate[bot] in [#9519](#9519)
- update taiki-e/install-action digest to 51cd0b8 by @renovate[bot] in
[#9531](#9531)
- exclude taiki-e/install-action from renovate by @jdx in
[#9532](#9532)
- update rust crate blake3 to v1.8.5 by @renovate[bot] in
[#9533](#9533)

### 📦 Registry

- enable shellcheck on windows by @zeitlinger in
[#9487](#9487)
- add google-java-format by @zeitlinger in
[#9488](#9488)
- add expert
([aqua:expert-lsp/expert](https://github.com/expert-lsp/expert)) by
@AlternateRT in [#9498](#9498)
- update entry for checkmake by @eread in
[#9504](#9504)
- add systemctl-tui
([aqua:rgwood/systemctl-tui](https://github.com/rgwood/systemctl-tui))
by @2xdevv in [#9521](#9521)
- add codon by @3w36zj6 in
[#9538](#9538)
- add tool yr (backend:github:VirusTotal/yara-x) by @adam-moss in
[#9542](#9542)
- add tool betterleaks (backend:aqua/betterleaks/betterleaks) by
@adam-moss in [#9541](#9541)
- add `git-filter-repo` by @garysassano in
[#9550](#9550)
- add umoci
([aqua:opencontainers/umoci](https://github.com/opencontainers/umoci))
by @2xdevv in [#9555](#9555)
- add aqua backend for elixir-ls by @AlternateRT in
[#9557](#9557)
- deny inline backend options by @risu729 in
[#9565](#9565)

### Chore

- **(ci)** fail registry tests without summary by @jdx in
[#9559](#9559)
- **(ci)** use !cancelled() instead of always() for test-ci aggregator
by @jdx in [#9569](#9569)
- **(ci)** use namespace runners for ci jobs by @jdx in
[#9561](#9561)
- **(config)** deprecate shorthands_file setting by @risu729 in
[#9534](#9534)
- **(docs)** remove shrill.en.dev analytics script by @jdx in
[#9539](#9539)
- **(release)** replace bc with awk in release-plz star formatting by
@jdx in
[d7f177f](d7f177f)
- bump hk to 1.44.3 by @jdx in
[#9493](#9493)
- invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by @jdx in
[#9560](#9560)
- set dev profile debug to 1 by @jdx in
[#9572](#9572)

### New Contributors

- @kuboon made their first contribution in
[#9567](#9567)
- @AlternateRT made their first contribution in
[#9557](#9557)
- @2xdevv made their first contribution in
[#9555](#9555)
- @adam-moss made their first contribution in
[#9541](#9541)
- @jlarmstrongiv made their first contribution in
[#9496](#9496)
- @tjarvstrand made their first contribution in
[#9505](#9505)
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