jdx/mise · error

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

Error message

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

What it means

validate_path_string rejects `path:` tool values containing characters forbidden for paths (after the Windows backslash rewrite, which is reported separately as error 1494). The message names the first offending character, caught during ToolRequest construction via new_with_options.

Source

Thrown at src/toolset/tool_request.rs:691

/// 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 afd2eddd3a)

Solutions

  1. Remove/rename the quoted forbidden character in the path (the error names it).
  2. Ensure proper TOML quoting: wrap paths with spaces in double quotes and escape backslashes (`"C:\\tools"`) or use forward slashes (`path:C:/tools`).
  3. Trim whitespace and retype smart quotes as plain ASCII in the config.

Example fix

// before (mise.toml)
[tools]
"path:C:/my 'tools'" = "1"
// after
[tools]
"path:C:/my-tools" = "1"
Defensive patterns

Strategy: validation

Validate before calling

function isValidToolPath(p) {
  return typeof p === 'string' && p.length > 0 && !/["';`$&|<>(){}\n\r]/.test(p);
}
if (!isValidToolPath(p)) throw new Error(`bad path value: ${p}`);

Type guard

function isSafePath(v) { return typeof v === 'string' && v === v.trim() && !/[^\w :\\/().+-]/.test(v); }

Prevention

When it happens

Trigger: A `path:` value containing quotes, semicolons, control characters, or other disallowed symbols, e.g. `path:"C:/my tools;rm"` or a config value with a stray quote or newline.

Common situations: Quoting mistakes in mise.toml (unbalanced quotes end up inside the path), shell interpolation leaking separators, or copy-pasted paths containing smart quotes or trailing whitespace/control chars.

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