jdx/mise · error

invalid tool version

Error message

invalid tool version {s:?}: must not start with '-'

What it means

mise validates every tool version string before it is used to build a ToolRequest. A version beginning with '-' is rejected because backend install commands (e.g. `mise install foo@-v1`) could otherwise interpret the version as a CLI flag, which is an injection/safety hazard. Empty versions are allowed (they mean 'unspecified'), anything starting with '-' is not.

Solutions

  1. Correct the version string so it does not start with '-' (e.g. use 1.2.3 not -1.2.3).
  2. If the dash came from shell interpolation, quote/default the variable: ${VER:-1.2.3}.
  3. If you meant a ref, use the explicit prefix form (ref:main, tag:v1) instead of a dash-prefixed value.

Example fix

// before
mise use node@-18
// after
mise use node@18
Defensive patterns

Strategy: validation

Validate before calling

function validVersion(v) {
  return typeof v === 'string' && (v === '' || !v.startsWith('-'));
}

Prevention

When it happens

Trigger: Calling ToolRequest::new_with_options (or CLI forms like `mise install tool@-x`, `mise use tool@-x`) with a version string whose first character is '-'.

Common situations: Scripts interpolating empty/undefined shell variables producing accidental dashes; copy-pasting a diff line ('-1.2.3') as a version; typos like `foo@-latest`; generating version strings programmatically with flag-like values.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/toolset/tool_request.rs:718

/// install path names and (for vfox plugins) into `ctx.version` / `ctx.rootPath`
/// values that downstream Lua hooks often interpolate into shell commands.
///
/// The deny list is the minimum set of characters that can break out of either
/// a single- or double-quoted shell string, or that trigger expansion *inside*
/// double quotes: quotes themselves, backslash, backtick, and `$`. Plus control
/// characters (newlines split shell tokens) and `..` (filesystem traversal).
/// Everything else is allowed so legitimate version vocabulary (npm-style
/// semver ranges like `>=20 <21 || >=22` or `^1.0.0`, dates, channel names,
/// `lts/hydrogen`, etc.) continues to work — those characters are only
/// dangerous in *unquoted* shell context, which cannot occur without one of
/// the rejected expansion characters appearing first. Leading dashes are also
/// rejected so backend install tools cannot mistake a version for a CLI flag.
fn validate_version_string(s: &str) -> Result<()> {
    if s.is_empty() {
        return Ok(());
    }
    if s.starts_with('-') {
        bail!("invalid tool version {s:?}: must not start with '-'");
    }
    if s.contains("..") {
        bail!("invalid tool version {s:?}: contains path-traversal sequence");
    }
    if let Some(c) = s.chars().find(|c| is_forbidden_version_char(*c)) {
        bail!("invalid tool version {s:?}: contains forbidden character {c:?}");
    }
    Ok(())
}

/// Validate `ref:`/`branch:`/`tag:`/`rev:` values. Same character rules as
/// version strings: branch/tag names already use the same broad vocabulary
/// (`/`, `+`, `-`, etc.), so only shell-quote-breaking characters and leading
/// dashes need rejection. Kept as a separate function for distinct error
/// messages.
fn validate_ref_string(s: &str) -> Result<()> {
    if s.is_empty() {
        return Ok(());

View on GitHub (pinned to 533346cc37)