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
- Add the manager prefix, e.g. apt:curl instead of curl
- Check for a stray or missing colon in the spec
- 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
- Always prefix package names with the manager and a colon
- Validate specs against /^[^:]+:[^:]+$/ before invoking mise
- Keep bootstrap packages config entries in manager:package form
- Copy specs from docs including the manager prefix
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
- invalid system package spec: expected
- invalid system package spec
- no packages requested for manager
- cannot export ' ': use a shorthand attribute such as…
- Check command must not be empty
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)