jdx/mise · error

invalid lockfile format for tool

Error message

invalid lockfile format for tool {short}: expected array ([[tools.{short}]])

What it means

In mise.lock the `tools` section must map each tool shorthand to an array of version entries, i.e. TOML written as `[[tools.<short>]]` repeated per version. If the value for a tool is any other TOML type (a plain table, string, etc.), the parser bails with this message naming the offending tool.

Solutions

  1. Fix the TOML in mise.lock: use double-bracket array-of-tables syntax, one [[tools.<short>]] block per version.
  2. Remove the malformed tool entry and re-run `mise lock` to regenerate it.
  3. Restore mise.lock from version control if a merge broke the structure.

Example fix

# before (mise.lock)
[tools.node]
version = "22"

# after
[[tools.node]]
version = "22"
Defensive patterns

Strategy: validation

Validate before calling

const tools = doc.get('tools');
for (const [name, v] of Object.entries(tools || {})) {
  if (!Array.isArray(v)) throw new Error(`tools.${name} must be an array of tables ([[tools.${name}]])`);
}

Type guard

function isArrayOfTables(v) { return Array.isArray(v) && v.every(x => typeof x === 'object' && x !== null); }

Prevention

When it happens

Trigger: Parsing a mise.lock where `tools.<name>` is not an array — e.g. someone wrote `[tools.node]` (single table) instead of `[[tools.node]]`, or merged versions into an inline table.

Common situations: Hand-authoring or hand-editing lockfile tool entries; merge conflicts resolved into a single table; scripts generating lockfiles with the wrong TOML syntax (single vs double brackets).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/5b8935eb6d66c964. Report an issue: GitHub.

Appendix: source

Thrown at src/lockfile.rs:1220

        let tools: toml::Table = table
            .remove("tools")
            .unwrap_or(toml::Table::new().into())
            .try_into()?;

        let mut lockfile = Lockfile {
            lockfile_version,
            generated_header_url,
            ..Default::default()
        };

        for (short, value) in tools {
            let versions = match value {
                toml::Value::Array(arr) => arr
                    .into_iter()
                    .map(LockfileTool::try_from)
                    .collect::<Result<Vec<_>>>()?,
                _ => bail!(
                    "invalid lockfile format for tool {short}: expected array ([[tools.{short}]])"
                ),
            };
            let versions = versions
                .into_iter()
                .map(|mut entry| -> Result<_> {
                    if let Some(graph) = &mut entry.uv {
                        graph.resolve_path(path)?;
                    }
                    if let Some(graph) = &mut entry.aube {
                        graph.resolve_path(path)?;
                    }
                    Ok(entry)
                })
                .collect::<Result<Vec<_>>>()?;
            lockfile.tools.entry(short).or_default().extend(versions);
        }

View on GitHub (pinned to 533346cc37)