jdx/mise · error

invalid tool path : contains forbidden character

Error message

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

What it means

path: tool values have their own permissive but nonzero character allowlist (is_forbidden_path_char), since paths legitimately contain '/', spaces, etc. Any other forbidden character is rejected with a message naming the character, after Windows-specific backslash handling.

Solutions

  1. Remove/replace the flagged character (it is named in the message).
  2. Expand variables before writing config, or use mise templating/env correctly.
  3. On Unix, convert Windows separators: C:\tools\node -> C:/tools/node (or a POSIX path).

Example fix

// before
node = "path:$HOME/tools/'my node'"
// after
node = "path:~/tools/my-node"  # expand and avoid quote characters
Defensive patterns

Strategy: validation

Validate before calling

function validPath(p) {
  return typeof p === 'string' && p.length > 0 && !/[\'";$|&`<>]/.test(p);
}

Prevention

When it happens

Trigger: A path: value containing a character rejected by is_forbidden_path_char (e.g. quotes, ';', '|', control characters, or a stray '\' on non-Windows) passed through new_with_options.

Common situations: Shell interpolation injecting quotes/metacharacters into the path; path with unexpanded $VAR or ~ left literal in config; invisible characters from copy-paste; Windows-style backslash paths on Linux/macOS.

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/0a2ff7ab0b2d136b. Report an issue: GitHub.

Appendix: source

Thrown at src/toolset/tool_request.rs:781

/// see [`is_forbidden_path_char`].
fn validate_path_string(s: &str) -> Result<()> {
    if s.is_empty() {
        return Ok(());
    }
    if let Some(c) = s.chars().find(|c| {
        // Allow newlines/tabs/etc. in paths is still bad — keep control-char
        // and quote/expansion rejection, but allow `/` since paths need it.
        is_forbidden_path_char(*c)
    }) {
        // The only `\` that survives the rewrite is an extended-length or device prefix, so say
        // what is actually wrong instead of naming a character the user cannot avoid.
        #[cfg(windows)]
        if c == '\\' {
            bail!(
                "invalid tool path {s:?}: extended-length and device paths (\\\\?\\, \\\\.\\) are not supported"
            );
        }
        bail!("invalid tool path {s:?}: contains forbidden character {c:?}");
    }
    Ok(())
}

/// Rewrite `\` to `/` in a `path:` value on Windows.
///
/// `\` is the path separator there, not a shell metacharacter, so [`validate_path_string`] used to
/// reject every native path — anything copied out of Explorer or printed by `pwd`. Win32 accepts
/// `/` wherever it accepts `\`, so rewriting is what makes those usable *without* letting a `\`
/// reach a vfox hook's `ctx.rootPath`, which is the thing the list exists to prevent (#9814). The
/// alternative — dropping `\` from the list on Windows — would have weakened that.
///
/// Extended-length and device prefixes (`\\?\`, `\\.\`) are left alone. Those are the one place
/// Windows does not accept `/`, so rewriting would hand back a path that looks right and does not
/// resolve; they keep being rejected, as they are today.
#[cfg(windows)]
fn windows_path_separators(s: &str) -> std::borrow::Cow<'_, str> {
    if s.starts_with(r"\\?\") || s.starts_with(r"\\.\") {

View on GitHub (pinned to 533346cc37)