jdx/mise · error

backend must be a string or table

Error message

backend must be a string or table

What it means

The registry TOML parser accepts a backend definition either as a plain string (shorthand) or as a table with fields like full/platforms/options. Any other TOML type (array, integer, boolean, etc.) falls into the `_` match arm and bails with this message.

Solutions

  1. Write the backend as a plain string: `backend = "github:user/repo"`.
  2. Or use the table form with fields like `backend = { full = "...", platforms = [...] }`.
  3. Remove stray brackets/quotes around the value and re-run the failing command.

Example fix

// before (registry TOML)
backend = ["github:jdx/mise-node"]
// after
backend = "github:jdx/mise-node"
Defensive patterns

Strategy: validation

Validate before calling

const b = table.get('backend');
if (typeof b !== 'string' && !(b && typeof b === 'object' && !Array.isArray(b)))
  throw new Error('backend must be a string or table');

Type guard

const isBackendValue = (v) => typeof v === 'string' || (v !== null && typeof v === 'object' && !Array.isArray(v));

Prevention

When it happens

Trigger: A registry entry's backend value written as a non-string, non-table type, e.g. `backend = ["github"]`, `backend = 1`, or `backend = true`, while `parse_backend` pattern-matches.

Common situations: Hand-editing registry/ TOML files and accidentally adding brackets around the backend name; tool-generated TOML emitting arrays for repeated keys; merging duplicate entries that TOML turns into an array.

Related errors


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

Appendix: source

Thrown at src/registry.rs:585

                    options
                        .iter()
                        .map(|(key, value)| {
                            let mut serialized = String::new();
                            value.serialize(toml::ser::ValueSerializer::new(&mut serialized))?;
                            Ok((leak_string(key.clone()), leak_string(serialized)))
                        })
                        .collect::<Result<Vec<_>>>()
                })
                .transpose()?
                .unwrap_or_default();
            Ok(RegistryBackend {
                full: leak_string(full.to_string()),
                platforms: leak_vec(platforms),
                min_version,
                options: leak_vec(options),
            })
        }
        _ => bail!("backend must be a string or table"),
    }
}

fn parse_registry_test(value: &toml::Value) -> Result<RegistryToolTest> {
    let table = value
        .as_table()
        .ok_or_else(|| eyre::eyre!("test must be a table"))?;
    let cmd = table
        .get("cmd")
        .and_then(toml::Value::as_str)
        .ok_or_else(|| eyre::eyre!("test.cmd must be a string"))?;
    let expected = table
        .get("expected")
        .and_then(toml::Value::as_str)
        .ok_or_else(|| eyre::eyre!("test.expected must be a string"))?;
    let tools = string_array(table.get("tools"), "test.tools")?;
    Ok(RegistryToolTest {
        cmd: leak_string(cmd.to_string()),

View on GitHub (pinned to 533346cc37)