Skip to content

fix(lockfile): don't propagate ad-hoc CLI overrides into the project lockfile#9562

Merged
jdx merged 2 commits intomainfrom
fix/release-plz-node-version
May 3, 2026
Merged

fix(lockfile): don't propagate ad-hoc CLI overrides into the project lockfile#9562
jdx merged 2 commits intomainfrom
fix/release-plz-node-version

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented May 3, 2026

Summary

mise x node@latest -- ... (and mise run-spawned mise x calls) is an ephemeral CLI override. With MISE_LOCKFILE=1, that install was being merged into the project's mise.lock under the config's existing request — e.g. mise.toml node = "24" paired with a freshly-installed 25.9.0. The lockfile entry then claimed the "24" request resolved to a 25.x version, which is nonsensical and breaks subsequent resolutions.

Why this matters

This is what was producing the recurring [autofix.ci] apply automated fixes commit on every release-plz PR (e.g. PR #9485 / ea7affd8eb3f46374a9451550e7b08f1bbcf6e07: node 24.15.0 → 25.9.0, platforms 11 → 1). mise run render calls mise x node@latest -- npx markdown-magic; on the autofix runner with MISE_LOCKFILE=1 set, that install bumped mise.lock's node entry every release cycle. If autofix is ever blocked or fails on the release branch, the release would ship with a stale/wrong lockfile.

Root cause

update_lockfiles's else-if branch (added in #9442 to support mise upgrade propagating to the lockfile) overwrites a config tool's lockfile entry with any newly installed ToolSource::Argument version that shares the short name. But upgrade.rs already remaps ArgumentMiseToml(...) before calling update_lockfiles (src/cli/upgrade.rs:383-396), so its tools go through the path-source if branch. The else-if branch only fires for tools that kept ToolSource::Argument — exactly the ad-hoc CLI overrides that shouldn't be persisted.

Fix

Compare the new install's request specifier to the config's. Only propagate if they match.

  • mise x node (no version) — with_default_to_latest rewrites the request to carry the config's version string, so the specifiers match and a legitimate fuzzy refresh still populates mise.lock (e.g. against an empty lockfile)
  • mise x node@24 when mise.toml says node = "24" — same specifier, propagates
  • mise x node@latest when mise.toml says node = "24""latest" != "24", skipped
  • mise x node@25 when mise.toml says node = "24""25" != "24", skipped
  • mise upgrade keeps working — its remap → MiseToml hits the if-branch, never the else-if
  • mise install (no args) keeps working — sources are MiseToml
  • mise install <tool> for a configured tool keeps working — get_requested_tool_versions (src/cli/install.rs:264-298) pulls the config's request when no version is given, so the source is MiseToml

Test plan

  • New e2e: e2e/cli/test_exec_lockfile — covers both directions:
    • mise x [email protected] and mise x dummy@latest must NOT mutate mise.lock when mise.toml says dummy = "1"
    • mise x dummy (no version) MUST populate an empty lockfile from the config-derived install
    • mise upgrade [email protected] MUST update the lockfile (regression check)
  • mise run test:e2e e2e/cli/test_upgrade e2e/cli/test_upgrade_latest_stale — green (regression check for fix(lockfile): update global lockfile on upgrade #9442)
  • mise run test:e2e e2e/cli/test_lock e2e/cli/test_lock_global e2e/cli/test_lock_latest e2e/cli/test_lock_creation e2e/cli/test_lock_local_config e2e/cli/test_lock_version e2e/cli/test_exec_latest — green
  • cargo test --all-features lockfile — 29 lockfile unit tests pass

🤖 Generated with Claude Code


Note

Medium Risk
Touches update_lockfiles behavior so some installs triggered by mise x/env overrides will no longer update mise.lock; this could affect workflows that relied on the previous (incorrect) propagation but is guarded by an e2e regression test.

Overview
Prevents mise.lock from being updated by ad-hoc version overrides (e.g. mise x tool@latest or MISE_<TOOL>_VERSION=...) when the override’s version specifier doesn’t match the project config’s requested specifier.

update_lockfiles now compares the new install’s request string against the config-derived request before propagating into the lockfile, skipping mismatches and logging a trace message. Adds an e2e test (e2e/cli/test_exec_lockfile) covering override non-propagation, expected propagation for mise x tool without a version, and mise upgrade still updating the lockfile.

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 3, 2026

Greptile Summary

This PR fixes a bug where mise x tool@<version> (and MISE_<TOOL>_VERSION=... env overrides) were incorrectly mutating the project mise.lock by pairing a config-declared request (e.g. node = "24") with an ad-hoc install of a different version (e.g. 25.x). The fix adds a specifier-equality guard in update_lockfiles's else if branch, and the mise.lock change rolls back the stale node entry that caused the recurring CI commits.

Confidence Score: 5/5

Safe to merge — targeted, well-tested fix with no P0/P1 issues found.

The fix is a single, clearly-scoped guard in one branch of update_lockfiles. All affected code paths are covered by the new e2e test and existing regression tests. The mise.lock rollback confirms the fix resolves the real-world symptom. No edge cases or logic errors were found.

No files require special attention.

Important Files Changed

Filename Overview
src/lockfile.rs Adds a version-specifier equality guard before propagating a non-path-sourced install into the lockfile; logic is correct and well-commented.
e2e/cli/test_exec_lockfile New e2e test covering all key scenarios: CLI override non-propagation, env-var override non-propagation, no-version mise x propagation, mise upgrade propagation, and empty-lockfile population.
mise.lock Rolls back node from 25.9.0 (incorrectly written by the bug) to 24.15.0 and removes the stale linux-x64 platform block; confirms the fix works on the real lockfile.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[update_lockfiles called\nwith new_versions] --> B{new_version has\nsource path?}
    B -->|Yes — MiseToml source| C[Direct update:\nreplace/insert lockfile entry\nfor that config path]
    B -->|No — Argument or\nEnvironment source| D{Tool short-name\nexists in lockfile?}
    D -->|No| E[Skip — tool not\nin project config]
    D -->|Yes| F{Exactly one\nconfig entry?}
    F -->|No| G[Skip — ambiguous\nconfig entries]
    F -->|Yes| H{new_version.request.version\n== config request.version?}
    H -->|No — specifier mismatch\ne.g. 'latest' vs '24'| I[Skip — ad-hoc CLI/env override\ndoes NOT update lockfile]
    H -->|Yes — same specifier\ne.g. both '24'| J[Propagate: overwrite\nrequest with config request\nand update lockfile entry]

    style I fill:#f66,color:#fff
    style J fill:#6a6,color:#fff
    style C fill:#6a6,color:#fff
Loading

Reviews (6): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

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 updates the render-help task in tasks.toml by removing the explicit node@latest version from the mise x command. The review feedback suggests that the mise x -- prefix is redundant because the task already executes within the project's environment, and recommends removing it entirely to simplify the command and avoid unnecessary CLI invocations.

Comment thread tasks.toml Outdated
run = [
"mise render-help",
"mise run show-output-on-failure -- mise x node@latest -- npx markdown-magic",
"mise run show-output-on-failure -- mise x -- npx markdown-magic",
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

The mise x -- prefix is redundant in this context. Since render:help is defined as a mise task, it automatically executes within the project's environment, meaning the pinned Node.js version and npx are already present in the PATH. Removing the redundant mise x call simplifies the task and avoids an unnecessary extra invocation of the mise CLI, which aligns with the objective of preventing unintended mise.lock updates.

  "mise run show-output-on-failure -- npx markdown-magic",

@jdx jdx force-pushed the fix/release-plz-node-version branch from 8505e76 to dbc8c44 Compare May 3, 2026 13:27
@jdx jdx changed the title fix(release): stop render:help bumping node in mise.lock fix(lockfile): don't propagate ad-hoc CLI overrides into the project lockfile May 3, 2026
@jdx jdx force-pushed the fix/release-plz-node-version branch from 8d1d43c to e076560 Compare May 3, 2026 13:38
…ckfile

`mise x node@latest -- ...` (and `mise run`-spawned exec) installs an
ephemeral, CLI-supplied tool version. With `MISE_LOCKFILE=1`, that
install was being merged into the project's `mise.lock` under the
config's existing request — e.g. mise.toml `node = "24"` paired with a
freshly-installed `25.9.0`. The lockfile entry then claimed the "24"
request resolved to 25.x, which is nonsensical and breaks subsequent
`mise install` resolutions.

This is what was producing the recurring autofix commit on each
`release-plz` PR (e.g. #9485 / ea7affd). `mise run render` calls
`mise x node@latest -- npx markdown-magic`; on the autofix runner with
`MISE_LOCKFILE=1` set, that bumped `mise.lock`'s node entry every cycle.

`update_lockfiles`'s else-if branch was added in #9442 to support
`mise upgrade`, but `upgrade.rs` already remaps `ToolSource::Argument`
→ `ToolSource::MiseToml` before calling `update_lockfiles`, so its
tools go through the path-source if-branch. The else-if branch only
fires for tools that *kept* `ToolSource::Argument` — exactly the
ad-hoc CLI overrides that shouldn't be persisted.

Skip the propagation only when the new install's request specifier
doesn't match the config's. `mise x node` (no version) is rewritten by
`with_default_to_latest` to carry the config's version string, so the
specifiers match and a legitimate fuzzy refresh still updates the
lockfile. `mise x node@latest` carries a "latest" specifier that
doesn't match `node = "24"`, so the override is correctly skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@jdx jdx force-pushed the fix/release-plz-node-version branch from e076560 to 3b7233a Compare May 3, 2026 13:42
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented May 3, 2026

Re: the ToolSource::Environment concern — addressed in the latest commit (3b7233a).

The reviewed commit (dbc8c44) used a source-only check (matches!(source, Argument)), which would indeed have left Environment exposed. The current fix instead compares request version specifiers (new_version.request.version() != request.version()), which naturally covers both:

  • mise x node@latest → request version "latest" ≠ config "24" → skipped
  • MISE_NODE_VERSION=25 mise installEnvironment("MISE_NODE_VERSION", "25") request version "25" ≠ config "24" → skipped

Added an explicit env-var case to e2e/cli/test_exec_lockfile:

MISE_DUMMY_VERSION=2 mise install
assert_contains "cat mise.lock" "1.0.0"
assert_not_contains "cat mise.lock" "2.0.0"

The version-specifier match also handles the inverse: when the override happens to match the config (e.g. MISE_DUMMY_VERSION=1 with dummy = "1"), the install legitimately resolves the same request, so the lockfile updates as expected.

This comment was generated by an AI coding assistant.

@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 22.1 ± 0.5 21.2 25.1 1.00
mise x -- echo 23.2 ± 0.8 21.7 28.3 1.05 ± 0.04

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 env 22.4 ± 0.6 21.0 25.2 1.00
mise env 22.8 ± 1.0 21.3 37.5 1.02 ± 0.05

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 hook-env 22.7 ± 1.0 21.5 36.5 1.00
mise hook-env 23.0 ± 0.6 22.0 25.3 1.01 ± 0.05

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 ls 20.2 ± 0.7 19.0 23.9 1.00 ± 0.04
mise ls 20.2 ± 0.5 19.4 24.7 1.00

xtasks/test/perf

Command mise-2026.4.28 mise Variance
install (cached) 150ms 156ms -3%
ls (cached) 76ms 79ms -3%
bin-paths (cached) 81ms 83ms -2%
task-ls (cached) 829ms 795ms +4%

@jdx jdx enabled auto-merge (squash) May 3, 2026 14:27
@jdx jdx merged commit 0262ccb into main May 3, 2026
34 checks passed
@jdx jdx deleted the fix/release-plz-node-version branch May 3, 2026 14:30
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