jdx/mise · error

lockfile contains multiple optionless resolutions for {short

Error message

lockfile contains multiple optionless resolutions for {short}@{specifier}

What it means

During resolution lookup with the legacy (optionless) fallback path, mise expects at most one lockfile entry for short@specifier without request options. If multiple optionless entries match, the resolution is ambiguous and mise bails instead of guessing.

Source

Thrown at src/lockfile.rs:3687

            if legacy_options_fallback && !request_options.is_empty() {
                let legacy = tools
                    .iter()
                    .filter(|tool| tool.specifiers.contains(specifier) && tool.options.is_empty())
                    .collect_vec();
                match legacy.as_slice() {
                    [] => {}
                    [found] => {
                        trace!(
                            "[{short}@{specifier}] found {} in lockfile without options, keeping the version pin and dropping its artifact data",
                            found.version
                        );
                        return Ok(Some(lockfile_tool_with_request_options(
                            found,
                            request_options,
                        )));
                    }
                    _ => bail!(
                        "lockfile contains multiple optionless resolutions for {short}@{specifier}"
                    ),
                }
            }

            // Mixed-format monorepo migration can temporarily place legacy
            // entries without bindings in a version-1 file. Keep those pins
            // reachable until a successful full format upgrade binds them.
            let version_matches = |v: &LockfileTool| {
                v.specifiers.is_empty()
                    && lockfile_version_matches(prefix, &v.version)
                    && (!require_prefix_boundary
                        || lockfile_version_matches_prefix_boundary(prefix, &v.version))
            };
            let matching = tools
                .iter()
                .filter(|v| version_matches(v) && &v.options == request_options)
                .collect_vec();

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Deduplicate the [[tools.<short>]] entries in mise.lock so only one optionless entry exists for that specifier
  2. Regenerate mise.lock from scratch with `mise lock` after backing it up
  3. Complete the monorepo lockfile migration (run `mise lock --upgrade` at the root) so legacy duplicate entries are collapsed

Example fix

# before (mise.lock)
[[tools.node]]
version = "20.0.0"
[[tools.node]]
version = "20.0.0"
# after
[[tools.node]]
version = "20.0.0"
Defensive patterns

Strategy: validation

Validate before calling

// Detect duplicate optionless entries for a tool before lookup
fn optionless_dupes(entries: &[TomlToolEntry]) -> bool {
    entries.iter().filter(|e| e.options.is_empty()).count() > 1
}

Prevention

When it happens

Trigger: Resolving short@specifier against a lockfile where the legacy/optionless matching path finds two or more entries with no options. Caused by duplicated lockfile rows from merges, concurrent lock writes, or hand edits — often seen during mixed-format monorepo migration.

Common situations: Git merge duplicated optionless [[tools.x]] entries; an interrupted `mise lock` left multiple rows; migration between lockfile formats temporarily created both legacy and new entries that collide on lookup.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/96732af045bcc3f2. Report an issue: GitHub.