jdx/mise · error · eyre::Report

invalid system package spec

Error message

invalid system package spec '{spec}': expected '<manager>:<package>' (e.g. "apt:curl")

What it means

parse_spec splits a system package spec like "apt:curl" on the first ':'. The input had no valid manager/package split, so it cannot be routed to a package manager; the spec in the config key or CLI argument is the input at fault.

Solutions

  1. Add the manager prefix, e.g. apt:curl instead of curl
  2. Check for a stray or missing colon in the spec
  3. Fix the entry in your bootstrap packages config file

Example fix

// before
packages = ["curl"]
// after
packages = ["apt:curl"]
Defensive patterns

Strategy: validation

Validate before calling

const valid = /^[^:\s]+:[^:\s]+$/.test(spec); // e.g. "apt:curl"

Prevention

When it happens

Trigger: Calling mise bootstrap packages (parse) with a spec like 'curl' (no colon), ':curl' (empty manager), or 'apt:' (empty package).

Common situations: Typing a package name without the manager prefix in bootstrap packages config or CLI; copy-pasting specs from docs that only show the package name; TOML entries missing the manager.

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/107003a966f9ff14. Report an issue: GitHub.

Appendix: source

Thrown at src/system/mod.rs:412

    BrewCask { adopt: BTreeSet<String> },
}

impl ManagerPackageOptions {
    #[cfg(unix)]
    pub(crate) fn brew_cask_adopt(&self, name: &str) -> bool {
        matches!(self, Self::BrewCask { adopt } if adopt.contains(name))
    }
}

/// Split a `"manager:package"` spec (config key or CLI argument). Only the
/// first `:` separates — apt arch qualifiers ("apt:gcc:arm64") and brew
/// versioned formula names ("brew:postgresql@17") stay part of the package.
pub(crate) fn parse_spec(spec: &str) -> eyre::Result<(String, String)> {
    match spec.split_once(':') {
        Some((mgr, pkg)) if !mgr.is_empty() && !pkg.is_empty() => {
            Ok((mgr.to_string(), pkg.to_string()))
        }
        _ => bail!(
            "invalid system package spec '{spec}': expected '<manager>:<package>' (e.g. \"apt:curl\")"
        ),
    }
}

/// Split a `mise bootstrap packages use` spec `manager:package[@version]` into its parts.
///
/// `@version` mirrors `mise use tool@version`; `@latest` (or no `@`) means no
/// pin. brew and brew-cask are exempt from `@` parsing: `@` is part of
/// Homebrew names (`postgresql@17` — that name IS brew's versioning
/// mechanism), and bottles/casks can't be installed at a pinned version
/// anyway. mas uses numeric ADAM IDs only.
pub(crate) fn parse_use_spec(spec: &str) -> eyre::Result<(String, PackageRequest)> {
    let (mgr, rest) = parse_spec(spec)?;
    let rest = normalize_use_spec_package_name(&mgr, &rest)?;
    validate_package_name(&mgr, rest)?;
    if is_opaque_package_manager(&mgr) {
        return Ok((

View on GitHub (pinned to 533346cc37)