mise lockfiles: why locked=true needs lockfile=true
A missing lockfile setting left mise warning about unresolved tools; the CI lock check also depended on generating locks on glibc rather than musl.
Every CI step printed the following warning twice:
mise WARN Failed to resolve tool version list for node: node@26.5.0: node@26.5.0 is not in the lockfile
hint: Run `mise install` without --locked to update the lockfile
mise WARN Failed to resolve tool version list for python: python@3.14.6: python@3.14.6 is not in the lockfile
The installs and builds completed, but every mise x invocation printed the warning. The output showed that lockfile enforcement was incomplete.
The Problem
Note
This is a point-in-time account of mise lockfile behavior on glibc and musl hosts. The tool versions, platform set, and lockfile contents are the values from this investigation.
The repository pins every tool in mise.toml and ships a mise.lock with checksummed download URLs. The settings block was:
# mise.toml
[settings]
minimum_release_age = "1d"
experimental = true
locked = true
npm.package_manager = "npm"
locked = true is intended to fail if a tool lacks a pre-resolved URL in the lockfile for the current platform. In CI, this prevents resolution through external APIs and makes a missing entry a build failure.
The command warned instead. The first check was whether node and python were missing from mise.lock.
What Changed
The lockfile was complete. node had all seven platforms pinned with checksums and URLs, including linux-x64-musl, the platform used inside the CI node:*-alpine images. git log -S 'tools."node"' -- mise.lock returned no result because it searched for a quoted key. The entry was [[tools.node]], with no quotes. The warning came from the lockfile mode setting.
The fix was one line. The mise settings docs describe locked this way:
When enabled and lockfile is also enabled,
mise installfails if tools lack pre-resolved URLs in the lockfile for the current platform.
locked requires lockfile. The repository had locked = true but did not set lockfile = true. As a result, each mise x resolved the version list and printed the warning. The lockfile docs describe lockfile = true as the setting that enables lockfile mode before locked enforces it.
Layer one of the fix:
# mise.toml
[settings]
minimum_release_age = "1d"
experimental = true
lockfile = true # was missing: enables lockfile mode
locked = true # now actually enforces (needs lockfile)
lockfile_platforms = ["linux-x64", "linux-x64-musl", "macos-arm64"]
npm.package_manager = "npm"
lockfile_platforms trims the lock to the platforms the project actually uses: CI runs in node:*-alpine images (Alpine is musl-based, so mise detects linux-x64-musl inside the containers), and developers are on Apple Silicon. That drops linux-arm64, windows-x64, and Intel macOS, shrinking mise.lock and speeding up resolution.
After regenerating, mise install --locked ran clean:
mise all tools are installed
No warnings.
Musl and glibc lockfile generation
After removing the warnings, I checked whether the CI gate regenerated the lockfile and compared it with the committed file. The repository already used this pattern with pnpm (check:lockfile-sync), so I applied it to mise:
# what I tried first
toolchain:mise-lock-sync:
extends: .monorepo_workspace_lint_template # runs in node:*-alpine
script:
- ./bin/mise lock
- git diff --exit-code mise.lock
It failed on the first pipeline run. The diff showed node and python being rewritten to this:
[[tools.node]]
version = "26.5.0"
backend = "core:node"
[tools.node.options]
compile = "true"
[tools.node."platforms.linux-x64-musl"]
checksum = "sha256:b3db373d..."
url = "https://nodejs.org/dist/v26.5.0/node-v26.5.0.tar.gz"
install = "source"
install = "source", compile = "true". On the musl host, mise resolved node and python to source builds because prebuilt musl binaries were unavailable for those tools. The committed lockfile contained prebuilt binaries generated on a glibc host or macOS. mise lock on Alpine therefore produced a different file from mise lock on glibc, and the gate reported a diff caused by the platform.
mise lock is not platform-independent. It reflects the host’s ability to find prebuilts. A lockfile committed from glibc or macOS will not regenerate cleanly on musl.
Then I looked one directory over and found this already in ci/projects/supply-chain.yml:
mise-lock:validate:
stage: build
image: *renovate_image # renovate/renovate, a glibc image
variables:
MISE_SAFE: "1"
script:
- ./bin/mise --version
- ./bin/mise lock
- git diff --exit-code -- mise.lock
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" || ...
changes: [mise.toml, mise.lock, bin/mise, ci/projects/supply-chain.yml]
The gate already existed. It ran on the glibc renovate/renovate image, where mise lock matches the committed lockfile. It passed on my branch. I deleted my duplicate and documented the existing one instead.
Results
- The
not in the lockfilewarnings are gone.lockfile = truecompletes the subsystem; with a complete lockfile,mise xresolves from it directly. mise.lockshrank from seven platforms to three.- The existing
mise-lock:validategate catches future drift (a Renovate bump, a manual pin, or a platform change that touchesmise.tomlwithout refreshing the lock). It runs on glibc, so it is stable. - One doc page (
docs/dev-tools/mise-lock.md) records the model, the gate, and the musl-vs-glibc caveat.
What I’d Do Differently
The first CI attempt created a duplicate gate before checking whether one already existed. Searching ci/ for mise.lock would have found mise-lock:validate.
The initial diagnosis also took longer because git log -S searched for a quoted key that was unquoted in the file. rg '^\[\[tools\.node' mise.lock identifies the entry directly.
The investigation showed that a strict setting can depend on a second setting, and that lockfile generation can vary with the libc and platform used to generate it.
Renovate lock refresh
Renovate’s mise manager bumps versions in mise.toml, but it will not refresh mise.lock on its own. Per the Renovate mise manager docs, running mise lock counts as an unsafe execution because it can run repository-defined behavior:
Self-hosted administrators must explicitly allow this by including
misein the globalallowedUnsafeExecutionssetting.
Until "mise" lands in the runner’s allowedUnsafeExecutions, a Renovate mise MR will bump mise.toml and leave mise.lock stale, so mise-lock:validate fails and the MR needs a manual mise lock before it can merge. The gate makes that drift loud instead of silent. Enabling allowedUnsafeExecutions: ["mise"] plus lockFileMaintenance on the runner is the remaining follow-up.
References
- mise.lock Lockfile: what
lockfileandlockedactually do, and thatlockedrequireslockfile. - mise settings:
locked,lockfile,lockfile_platforms. - Renovate mise manager: the trust model for
mise lockandallowedUnsafeExecutions. - Renovate self-hosted configuration: the
allowedUnsafeExecutionsarray, including"mise".
This post was written with AI assistance.