Skip to content

fix(install): run tool postinstall against the just-installed version#10415

Merged
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix-postinstall-locked-version
Jun 14, 2026
Merged

fix(install): run tool postinstall against the just-installed version#10415
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix-postinstall-locked-version

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Problem

A tool-level postinstall hook can run against a different version of the tool than the one just installed. Reported in #10347:

[tools.python]
version = "3"
postinstall = "pip install numpy pandocfilters ptpython setuptools watchfiles"

Upgrading to Python 3.14.6 ran the hook''s pip from the previous 3.14.5 install, so the packages were installed into the 3.14.5 tree — which mise then uninstalled. The docs promise the tool''s bin path is on PATH during the hook.

Root cause

run_postinstall_hook builds the hook''s PATH from list_bin_paths, whose default resolves tv.runtime_path(). For a fuzzy version request (e.g. version = "3"), runtime_path() returns the runtime symlink installs/python/3. During mise up that symlink still points at the previous version, because runtime symlinks are only rebuilt after all installs finish — while the postinstall hook runs during the install. So pip resolved from the stale 3.14.5 tree.

Fix

Resolve the postinstall hook''s bin paths against the exact version just installed by locking a clone of the ToolVersion:

let tv_exact = tv.clone().with_locked(true);
let bin_paths = self.list_bin_paths(&ctx.config, &tv_exact).await?;

runtime_path() returns install_path() (the concrete version) when locked, so the hook''s PATH points at the version just installed. A new ToolVersion::with_locked builder sets this; locked is already ignored by Eq/Hash, so the install-path cache key is unchanged. The backend list_bin_paths indirection is preserved (aqua etc. still work via their install-path-based dirs).

Testing

  • Unit test: with_locked(true).runtime_path() resolves to install_path() for a fuzzy request.
  • cargo fmt --all -- --check passes.

Note: the $MISE_TOOL_INSTALL_PATH env var mise already sets for the hook remains a deterministic workaround for older versions.

Addresses #10347

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Post-install steps now consistently derive environment variables and PATH from the exact version just installed, avoiding stale binaries when fuzzy or ranged versions are involved.
  • Tests
    • Added a regression test to verify that locked runtime paths use the concrete install path rather than a fuzzy runtime symlink target.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b823d46f-88a4-434c-98f3-8174bf8597e9

📥 Commits

Reviewing files that changed from the base of the PR and between b6d0bd2 and 0c3a846.

📒 Files selected for processing (2)
  • src/backend/mod.rs
  • src/toolset/tool_version.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/backend/mod.rs
  • src/toolset/tool_version.rs

📝 Walkthrough

Walkthrough

ToolVersion gains a with_locked() -> Self method that clones the version and sets its locked flag, causing runtime_path() to return install_path() directly. run_postinstall_hook now clones tv with with_locked() to derive tv_exact and passes it to list_bin_paths and environment setup, replacing the prior use of the original (possibly fuzzy-symlinked) tv. A regression test verifies the behavior for fuzzy version requests.

Changes

Postinstall hook bin path fix

Layer / File(s) Summary
with_locked method definition
src/toolset/tool_version.rs
Adds ToolVersion::with_locked(mut self) -> Self that clones and sets the internal locked flag, pinning runtime_path() to install_path() during postinstall.
Postinstall hook bin path fix
src/backend/mod.rs
run_postinstall_hook creates tv_exact via tv.clone().with_locked() and uses it for both exec_env computation and list_bin_paths to resolve exact bin paths from the newly installed version instead of a potentially stale fuzzy symlink.
Regression test
src/toolset/tool_version.rs
Adds with_locked_runtime_path_uses_install_path_for_fuzzy_request unit test confirming that a fuzzy request resolved to a concrete version returns install_path() from runtime_path() when locked, preventing symlink staleness.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • jdx/mise#10162: Both PRs fix hook environment PATH computation to avoid using stale version-derived paths; this PR locks ToolVersion for exact bin path resolution while the related PR filters inactive install-dir prefixes from PATH.
  • jdx/mise#10245: Both PRs adjust how postinstall and bin discovery derive paths from ToolVersion; this PR locks the version for concrete install_path resolution while the related PR updates HTTP install/bins logic to normalize install_path consistently.

Poem

🐇 A fuzzy "3" once led astray,
the postinstall hook found yesterday's bins that day.
Now with_locked() sets the path just right,
install_path() shines in the postinstall light!
No more stale symlinks, the rabbit hops free~ 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: fixing tool postinstall hooks to run against the just-installed version. It matches the PR's core objective and is concise and specific.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where a tool-level postinstall hook could run against a stale version of the tool: for fuzzy version requests (e.g. version = "3"), the runtime symlink still pointed at the previous install during mise up because symlinks are only rebuilt after all installs finish. The fix clones the ToolVersion with with_locked() so both exec_env and list_bin_paths resolve through install_path() (the concrete version just installed) rather than the stale symlink.

  • Adds ToolVersion::with_locked(), a builder that sets locked = true causing runtime_path() to return install_path() directly, bypassing fuzzy symlink resolution.
  • Both exec_env and list_bin_paths in run_postinstall_hook now use the locked clone (tv_exact), addressing the stale-PATH bug and also the previously noted staleness risk for env vars like PYTHONHOME/JAVA_HOME.
  • A regression test reproduces the exact stale-symlink state from Tool postinstall command doesn't use the version being installed #10347 and asserts correct before/after behavior.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to run_postinstall_hook, the new with_locked() builder is simple and internally consistent with the existing locked flag semantics, and both affected call-sites (exec_env and list_bin_paths) now use the pinned version.

The fix correctly addresses the root cause: runtime_path() already short-circuits to install_path() when locked == true (line 204–206), so setting the flag on a clone before the hook runs is the right minimal fix. Both environment-variable resolution (exec_env) and PATH resolution (list_bin_paths) now use the locked clone, closing both stale-reference paths in one change. The unit test faithfully recreates the stale-symlink scenario from the bug report and asserts the expected behavior in both the broken and fixed states. No existing call-sites of with_locked exist outside this PR, and locked is already excluded from Eq/Hash, so cache semantics are unaffected.

No files require special attention.

Important Files Changed

Filename Overview
src/toolset/tool_version.rs Adds with_locked() builder that sets locked = true so runtime_path() returns install_path() instead of a fuzzy runtime symlink; includes a thorough regression test with a stale-symlink scenario.
src/backend/mod.rs run_postinstall_hook now clones tv with with_locked() and passes tv_exact to both exec_env and list_bin_paths, ensuring the hook's environment and PATH resolve against the just-installed version rather than a stale fuzzy symlink.

Reviews (4): Last reviewed commit: "fix(install): run tool postinstall again..." | Re-trigger Greptile

Comment thread src/toolset/tool_version.rs Outdated
@JamBalaya56562 JamBalaya56562 force-pushed the fix-postinstall-locked-version branch 2 times, most recently from 82cdc20 to b6d0bd2 Compare June 13, 2026 23:53
@JamBalaya56562

Copy link
Copy Markdown
Contributor Author

Also addressed the "Comments Outside Diff" note about exec_env still receiving the unlocked tv: run_postinstall_hook now builds tv_exact = tv.clone().with_locked() once and passes it to both exec_env and list_bin_paths. So a postinstall hook's environment (e.g. a backend deriving PYTHONHOME/JAVA_HOME/virtualenv roots from runtime_path()) and its PATH (pip, etc.) now both resolve to the exact version just installed rather than the stale fuzzy symlink.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/toolset/tool_version.rs`:
- Around line 838-852: The regression test in the ToolVersion fuzzy version
resolution scenario does not actually set up the problematic case it is supposed
to validate. The test needs to be hardened by creating a stale fuzzy runtime
symlink (such as installs/dummy/3 pointing to a previous version) before
constructing the ToolVersion, and then add explicit assertions to verify the
difference between unlocked and locked behavior: the unlocked runtime_path()
call should return the stale symlink path (demonstrating the bug), while the
with_locked().runtime_path() call should return the correct install_path
(demonstrating the fix). This ensures the test will catch regressions if
with_locked() is broken in the future.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 68238779-20eb-4324-b910-39926254e208

📥 Commits

Reviewing files that changed from the base of the PR and between 82cdc20 and b6d0bd2.

📒 Files selected for processing (2)
  • src/backend/mod.rs
  • src/toolset/tool_version.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/backend/mod.rs

Comment thread src/toolset/tool_version.rs
A tool-level `postinstall` hook resolved its bin PATH from the request's runtime
path. For a fuzzy version request (e.g. `[tools.python] version = "3"`) that is
the runtime symlink `installs/python/3`, which during `mise up` still points at
the previous version because symlinks are only rebuilt after all installs finish.
So `postinstall = "pip install ..."` for python 3.14.6 found pip from the old
3.14.5 tree and installed into it -- which mise then uninstalled (jdx#10347).

Resolve both the hook's environment (exec_env) and its bin paths against the
exact version just installed by locking a clone of the ToolVersion (runtime_path()
then returns install_path() instead of the fuzzy runtime symlink). Add
ToolVersion::with_locked for this; `locked` is ignored by Eq/Hash so the
install-path cache key is unchanged.

Addresses discussion jdx#10347.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JamBalaya56562 JamBalaya56562 force-pushed the fix-postinstall-locked-version branch from b6d0bd2 to 0c3a846 Compare June 14, 2026 00:12
@jdx jdx merged commit 847f5f6 into jdx:main Jun 14, 2026
33 checks passed
@JamBalaya56562 JamBalaya56562 deleted the fix-postinstall-locked-version branch June 14, 2026 00:28
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.

2 participants