Skip to content

fix(lock): write tools to lockfile matching their source config#8012

Merged
jdx merged 4 commits intomainfrom
fix/lock-local-config
Feb 6, 2026
Merged

fix(lock): write tools to lockfile matching their source config#8012
jdx merged 4 commits intomainfrom
fix/lock-local-config

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 5, 2026

Summary

  • mise lock now groups tools by their source config file and writes each to the corresponding lockfile
  • Tools defined in mise.tomlmise.lock, tools in mise.local.tomlmise.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 #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


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.

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

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>
Copilot AI review requested due to automatic review settings February 5, 2026 11:46
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 mise lock command where all tools were incorrectly written to a single, highest-priority lockfile, typically mise.local.lock. This behavior led to locked versions of tools being overlooked by version control systems when they originated from committed configuration files like mise.toml. The implemented changes introduce a more precise locking mechanism, ensuring that tools are now written to the correct lockfile corresponding to their source configuration, thereby preventing the unintended omission of crucial locked versions and improving team collaboration.

Highlights

  • Granular Lockfile Generation: The mise lock command now groups tools based on their source configuration file (e.g., mise.toml or mise.local.toml) and writes them to the corresponding lockfile.
  • Correct Lockfile Assignment: Tools defined in mise.toml will now be written to mise.lock, and tools from mise.local.toml will be written to mise.local.lock, resolving the issue of locked versions being silently gitignored.
  • Refactored Lock Processing Logic: The main run method in the Lock command has been refactored to iterate and process tools for each distinct lockfile independently, improving clarity and correctness.
  • Updated Tool Grouping Function: The get_tools_to_lock function was renamed to get_tools_by_lockfile and its return type changed to a BTreeMap<PathBuf, Vec<...>> to explicitly support grouping tools by their target lockfile path.
  • Preserved --local Flag Behavior: The --local flag functionality remains unchanged, ensuring it restricts output to only mise.local.lock when used.
Changelog
  • src/cli/lock.rs
    • Refactored the run method to iterate over and process tools for multiple lockfiles independently, based on their source configuration.
    • Renamed the get_tools_to_lock function to get_tools_by_lockfile and updated its return type to a BTreeMap mapping lockfile paths to lists of tools.
    • Modified the internal logic for collecting and filtering tools to correctly assign them to their respective lockfiles, including handling of the --local flag and ToolSource paths.
Activity
  • No human activity (comments, reviews, etc.) has been recorded for this pull request yet.
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
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Copy link
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 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>
@github-actions
Copy link

github-actions bot commented Feb 5, 2026

Hyperfine Performance

mise x -- echo

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%

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON, but a Cloud Agent failed to start.

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>
@jdx jdx merged commit c39f568 into main Feb 6, 2026
35 checks passed
@jdx jdx deleted the fix/lock-local-config branch February 6, 2026 12:31
mise-en-dev added a commit that referenced this pull request Feb 7, 2026
### 🚀 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)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
…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>
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 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)
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