jdx/mise · error
invalid tool ref : contains forbidden character
Error message
invalid tool ref {s:?}: contains forbidden character {c:?} What it means
Ref strings are held to the same character rules as version strings: validate_ref_string rejects any ref containing a character disallowed by is_forbidden_version_char, naming the offending character. This keeps git ref arguments and derived paths safe.
Solutions
- Rename/choose a ref without forbidden characters (the message names the culprit).
- Trim and sanitize templated config values before writing mise.toml.
- Reference the commit SHA instead of an oddly named ref when renaming is impossible.
Example fix
// before
node = { version = "ref:feature/foo bar" }
// after
node = { version = "ref:feature/foo-bar" } # or a rev: SHA Defensive patterns
Strategy: validation
Validate before calling
function validRef(r) {
return typeof r === 'string' && !r.startsWith('-') && !r.includes('..') && !/[\s'";$|&`<>]/.test(r);
} Prevention
- Avoid branch names with spaces/special characters, or use the SHA.
- Expand command substitution before it reaches config files.
- Trim whitespace from templated values.
When it happens
Trigger: A ref:/branch:/tag:/rev: value containing shell metacharacters or whitespace (e.g. ref:"my branch", rev:$(cmd)) reaching new_with_options.
Common situations: Branch names with spaces or special characters pasted unquoted; command substitution accidentally left in config; trailing whitespace/newlines from editor or templating.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- invalid tool path : contains forbidden character
- invalid tool ref : must not start with
- invalid tool version
- action prediction payload is too large
- brew-cask: invalid command_wrapper environment name
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/3a643f48f708d9bf.
Report an issue: GitHub.
Appendix: source
Thrown at src/toolset/tool_request.rs:745
}
/// 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 533346cc37)