fix(lock): write tools to lockfile matching their source config#8012
fix(lock): write tools to lockfile matching their source config#8012
Conversation
Previously, `mise lock` would pick the first config file (highest priority) and write all tools to its corresponding lockfile. When `mise.local.toml` existed, this meant all tools were written to `mise.local.lock` even if they were defined in `mise.toml`. Now tools are grouped by their source config file and written to the corresponding lockfile: tools from `mise.toml` go to `mise.lock`, tools from `mise.local.toml` go to `mise.local.lock`. Fixes #8000 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary of ChangesHello @jdx, 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 a significant bug in the Highlights
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Pull request overview
This PR fixes mise lock to write tools to lockfiles matching their source config files, preventing locked versions from being inadvertently gitignored. Previously, all tools were written to the lockfile corresponding to the highest-priority config (often mise.local.lock), causing committed tools to be locked in gitignored files.
Changes:
- Introduced
get_tools_by_lockfile()to group tools by their source config's corresponding lockfile - Modified
run()to iterate over each lockfile group independently instead of writing to a single lockfile - Updated tool tracking to include lockfile path in the deduplication key, allowing the same tool in multiple lockfiles
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/cli/lock.rs
Outdated
| ) -> Vec<(crate::cli::args::BackendArg, crate::toolset::ToolVersion)> { | ||
| // Calculate target lockfile directory (same logic as get_lockfile_path) | ||
| ) -> BTreeMap<PathBuf, Vec<(crate::cli::args::BackendArg, crate::toolset::ToolVersion)>> { | ||
| // Calculate target lockfile directory from the first config file |
There was a problem hiding this comment.
This comment is misleading. The target lockfile directory is used as a filter to ensure tools come from the same directory, not to determine where tools are written. Consider updating to clarify this is used for filtering config files in the same directory.
| // Calculate target lockfile directory from the first config file | |
| // Use the first config file to determine the reference lockfile directory. | |
| // This directory is used to filter/group config files and tools that belong | |
| // to the same location, not to decide where tools themselves are written. |
src/cli/lock.rs
Outdated
| }; | ||
|
|
||
| let mut result: BTreeMap<PathBuf, Vec<_>> = BTreeMap::new(); | ||
| // Include lockfile path in seen key so the same tool can appear in different lockfiles |
There was a problem hiding this comment.
This comment could be clearer about why this is necessary. Consider explaining that this allows the same tool version to be locked in both mise.lock and mise.local.lock when defined in both config files.
| // Include lockfile path in seen key so the same tool can appear in different lockfiles | |
| // Include lockfile path in seen key so the same tool/version can be locked separately | |
| // in mise.lock and mise.local.lock when it is defined in both types of config files. |
There was a problem hiding this comment.
Code Review
This pull request correctly refactors the mise lock command to handle multiple lockfiles based on the source configuration files. The logic for grouping tools by their target lockfile and processing each group independently is well-implemented. This change effectively addresses the issue of locked versions being ignored when using both global and local configuration files.
I have one suggestion to improve performance by reducing PathBuf cloning within loops. Overall, this is a solid improvement to the locking mechanism.
Verifies that `mise lock` writes tools to the correct lockfile based on their source config: mise.toml tools go to mise.lock, and mise.local.toml tools go to mise.local.lock. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tools without a source path (e.g. from environment variables or CLI args) are not from any config file, so they should be skipped when --local is specified rather than being written to mise.local.lock. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.4 x -- echo |
21.2 ± 0.4 | 20.2 | 24.5 | 1.00 |
mise x -- echo |
21.6 ± 0.7 | 20.3 | 27.4 | 1.02 ± 0.04 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.4 env |
20.5 ± 0.5 | 19.5 | 22.9 | 1.00 |
mise env |
21.0 ± 0.5 | 19.7 | 22.2 | 1.02 ± 0.03 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.4 hook-env |
21.5 ± 1.0 | 20.4 | 37.3 | 1.00 |
mise hook-env |
21.7 ± 0.5 | 20.4 | 23.6 | 1.01 ± 0.05 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.4 ls |
19.4 ± 0.4 | 18.4 | 20.9 | 1.00 |
mise ls |
19.6 ± 0.5 | 18.5 | 21.5 | 1.01 ± 0.04 |
xtasks/test/perf
| Command | mise-2026.2.4 | mise | Variance |
|---|---|---|---|
| install (cached) | 115ms | 114ms | +0% |
| ls (cached) | 72ms | 73ms | -1% |
| bin-paths (cached) | 76ms | 76ms | +0% |
| task-ls (cached) | 548ms | 541ms | +1% |
Replace the complex BTreeMap grouping with two explicit passes: first non-local configs (mise.lock), then local configs (mise.local.lock). With --local, only the local pass runs. This is simpler to reason about, eliminates PathBuf keys in the seen set, and naturally handles overridden tools since each pass iterates only config files matching its locality. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
### 🚀 Features - **(env)** add shell-style variable expansion in env values by @jdx in [#8029](#8029) - **(list)** add --all-sources flag to list command by @TylerHillery in [#8019](#8019) ### 🐛 Bug Fixes - **(gem)** Windows support for gem backend by @my1e5 in [#8031](#8031) - **(gem)** revert gem.rs script newline change by @my1e5 in [#8034](#8034) - **(lock)** write tools to lockfile matching their source config by @jdx in [#8012](#8012) - **(ls)** sort sources deterministically in --all-sources output by @jdx in [#8037](#8037) - **(task)** auto-install tools from mise.toml for file tasks by @jdx in [#8030](#8030) ### 📚 Documentation - fix wrong positions of `mise run` flags by @muzimuzhi in [#8036](#8036) ### 📦️ Dependency Updates - update ghcr.io/jdx/mise:copr docker digest to 3e00d7d by @renovate[bot] in [#8023](#8023) - update ghcr.io/jdx/mise:alpine docker digest to 0ced1b3 by @renovate[bot] in [#8022](#8022) ### 📦 Registry - add tirith ([github:sheeki03/tirith](https://github.com/sheeki03/tirith)) by @sheeki03 in [#8024](#8024) - add mas by @TyceHerrman in [#8032](#8032) ### Security - **(deps)** update time crate to 0.3.47 to fix RUSTSEC-2026-0009 by @jdx in [#8026](#8026) ### New Contributors - @sheeki03 made their first contribution in [#8024](#8024) - @TylerHillery made their first contribution in [#8019](#8019) ## 📦 Aqua Registry Updates #### New Packages (1) - [`kubernetes-sigs/kubectl-validate`](https://github.com/kubernetes-sigs/kubectl-validate) #### Updated Packages (6) - [`flux-iac/tofu-controller/tfctl`](https://github.com/flux-iac/tofu-controller/tfctl) - [`gogs/gogs`](https://github.com/gogs/gogs) - [`j178/prek`](https://github.com/j178/prek) - [`syncthing/syncthing`](https://github.com/syncthing/syncthing) - [`tuist/tuist`](https://github.com/tuist/tuist) - [`yaml/yamlscript`](https://github.com/yaml/yamlscript)
…8012) ## Summary - `mise lock` now groups tools by their source config file and writes each to the corresponding lockfile - Tools defined in `mise.toml` → `mise.lock`, tools in `mise.local.toml` → `mise.local.lock` - Previously, all tools were written to whichever lockfile corresponded to the highest-priority config (typically `mise.local.lock`), which meant locked versions were silently gitignored ## Problem When both `mise.toml` (committed) and `mise.local.toml` (gitignored) exist, `mise lock` wrote everything to `mise.local.lock`. Team members never got locked versions because the lockfile was gitignored. ## Fix Instead of picking a single lockfile from the first (highest-priority) config file, `get_tools_by_lockfile()` now determines the correct lockfile for each tool based on its `ToolSource` config path. The `run()` method iterates over each lockfile group independently. The `--local` flag continues to work, restricting output to only `mise.local.lock`. Fixes jdx#8000 ## Test plan - [ ] Create a directory with `mise.toml` (tools) and `mise.local.toml` (env only), run `mise lock`, verify `mise.lock` is created (not `mise.local.lock`) - [ ] Create a directory with tools in both files, run `mise lock`, verify each tool goes to the correct lockfile - [ ] Verify `mise lock --local` still only writes `mise.local.lock` 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core lockfile write behavior and tool selection logic, which could mis-route tools or alter generated lockfiles in edge cases (e.g., overridden tools or tools without a source path). > > **Overview** > `mise lock` now **splits locking into two passes** so tools defined in committed config(s) are written to `mise.lock` and tools defined in local config(s) are written to `mise.local.lock`, instead of emitting everything to a single lockfile. > > Tool selection was updated to filter by each tool’s source config (`lockfile_path_for_config` locality) and by config directory, while preserving `--local` behavior to only write `mise.local.lock`; an e2e test (`test_lock_local_config`) was added to verify the new lockfile routing in mixed-config scenarios. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 974a1da. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
### 🚀 Features - **(env)** add shell-style variable expansion in env values by @jdx in [jdx#8029](jdx#8029) - **(list)** add --all-sources flag to list command by @TylerHillery in [jdx#8019](jdx#8019) ### 🐛 Bug Fixes - **(gem)** Windows support for gem backend by @my1e5 in [jdx#8031](jdx#8031) - **(gem)** revert gem.rs script newline change by @my1e5 in [jdx#8034](jdx#8034) - **(lock)** write tools to lockfile matching their source config by @jdx in [jdx#8012](jdx#8012) - **(ls)** sort sources deterministically in --all-sources output by @jdx in [jdx#8037](jdx#8037) - **(task)** auto-install tools from mise.toml for file tasks by @jdx in [jdx#8030](jdx#8030) ### 📚 Documentation - fix wrong positions of `mise run` flags by @muzimuzhi in [jdx#8036](jdx#8036) ### 📦️ Dependency Updates - update ghcr.io/jdx/mise:copr docker digest to 3e00d7d by @renovate[bot] in [jdx#8023](jdx#8023) - update ghcr.io/jdx/mise:alpine docker digest to 0ced1b3 by @renovate[bot] in [jdx#8022](jdx#8022) ### 📦 Registry - add tirith ([github:sheeki03/tirith](https://github.com/sheeki03/tirith)) by @sheeki03 in [jdx#8024](jdx#8024) - add mas by @TyceHerrman in [jdx#8032](jdx#8032) ### Security - **(deps)** update time crate to 0.3.47 to fix RUSTSEC-2026-0009 by @jdx in [jdx#8026](jdx#8026) ### New Contributors - @sheeki03 made their first contribution in [jdx#8024](jdx#8024) - @TylerHillery made their first contribution in [jdx#8019](jdx#8019) ## 📦 Aqua Registry Updates #### New Packages (1) - [`kubernetes-sigs/kubectl-validate`](https://github.com/kubernetes-sigs/kubectl-validate) #### Updated Packages (6) - [`flux-iac/tofu-controller/tfctl`](https://github.com/flux-iac/tofu-controller/tfctl) - [`gogs/gogs`](https://github.com/gogs/gogs) - [`j178/prek`](https://github.com/j178/prek) - [`syncthing/syncthing`](https://github.com/syncthing/syncthing) - [`tuist/tuist`](https://github.com/tuist/tuist) - [`yaml/yamlscript`](https://github.com/yaml/yamlscript)
Summary
mise locknow groups tools by their source config file and writes each to the corresponding lockfilemise.toml→mise.lock, tools inmise.local.toml→mise.local.lockmise.local.lock), which meant locked versions were silently gitignoredProblem
When both
mise.toml(committed) andmise.local.toml(gitignored) exist,mise lockwrote everything tomise.local.lock. Team members never got locked versions because the lockfile was gitignored.Fix
Instead of picking a single lockfile from the first (highest-priority) config file,
get_tools_by_lockfile()now determines the correct lockfile for each tool based on itsToolSourceconfig path. Therun()method iterates over each lockfile group independently.The
--localflag continues to work, restricting output to onlymise.local.lock.Fixes #8000
Test plan
mise.toml(tools) andmise.local.toml(env only), runmise lock, verifymise.lockis created (notmise.local.lock)mise lock, verify each tool goes to the correct lockfilemise lock --localstill only writesmise.local.lock🤖 Generated with Claude Code
Note
Medium Risk
Changes core lockfile write behavior and tool selection logic, which could mis-route tools or alter generated lockfiles in edge cases (e.g., overridden tools or tools without a source path).
Overview
mise locknow splits locking into two passes so tools defined in committed config(s) are written tomise.lockand tools defined in local config(s) are written tomise.local.lock, instead of emitting everything to a single lockfile.Tool selection was updated to filter by each tool’s source config (
lockfile_path_for_configlocality) and by config directory, while preserving--localbehavior to only writemise.local.lock; an e2e test (test_lock_local_config) was added to verify the new lockfile routing in mixed-config scenarios.Written by Cursor Bugbot for commit 974a1da. This will update automatically on new commits. Configure here.