jdx/mise · error

invalid tool ref {s:?}: contains forbidden character {c:?}

Error message

invalid tool ref {s:?}: contains forbidden character {c:?}

What it means

validate_ref_string rejects ref values containing any character matched by is_forbidden_version_char; the message names the first offending character. Refs share the version vocabulary to keep them safe as filenames and CLI arguments.

Source

Thrown at src/toolset/tool_request.rs:655

}

/// 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(());
    }
    if s.starts_with('-') {
        bail!("invalid tool ref {s:?}: must not start with '-'");
    }
    if s.contains("..") {
        bail!("invalid tool ref {s:?}: contains path-traversal sequence");
    }
    if let Some(c) = s.chars().find(|c| is_forbidden_version_char(*c)) {
        bail!("invalid tool ref {s:?}: contains forbidden character {c:?}");
    }
    Ok(())
}

/// Validate `path:` values. Filesystem paths legitimately contain `/`, spaces,
/// and many other characters, but the resolved path becomes `ctx.rootPath` /
/// `installPath` for path-mode tools and is interpolated into shell commands
/// by some plugin hooks. Reject the same shell-quote-breaking characters as
/// version strings — `$`, backtick, quotes, and `\` — so a hostile `path:`
/// entry in a project config cannot inject shell syntax. Path traversal is
/// intentionally not rejected here because `path:../tools/foo` is a normal
/// relative-path use case.
///
/// The list is written for a POSIX shell, which is why `\` is on it. On Windows `\` is a path
/// separator instead, so it is rewritten by [`windows_path_separators`] before it gets here rather
/// than being allowed through — see that function. The shell those hooks run through there is
/// `cmd.exe`, whose metacharacters are a different set, so a few more are rejected on Windows —
/// see [`is_forbidden_path_char`].

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Remove or rename the offending character in the branch/tag name (the error names it explicitly).
  2. Quote and trim shell expansions before composing the ref string.
  3. For unusual names, pin a commit with `rev:<sha>` instead of the raw name.

Example fix

// before
node = { tag = "v1.0 final" }
// after
node = { tag = "v1.0-final" }
Defensive patterns

Strategy: validation

Validate before calling

function isValidRefChars(ref) { return typeof ref === 'string' && !/[\s'";`$&|<>(){}\[\]\\]/.test(ref); }
if (!isValidRefChars(ref)) throw new Error(`ref has forbidden characters: ${ref}`);

Type guard

function isPlainRef(v) { return typeof v === 'string' && /^[\w./+-]+$/.test(v); }

Prevention

When it happens

Trigger: A `ref:`/`branch:`/`tag:`/`rev:` value containing characters like spaces, quotes, semicolons, or other disallowed symbols, e.g. `node@ref:my branch` or `tag = "v1.0 (rc)"`.

Common situations: Branch names with spaces or special characters produced by templating/shell expansion, quotes copied from web pages, or shell interpolation injecting separators into the ref slot.

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@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/3a643f48f708d9bf. Report an issue: GitHub.