jdx/mise · error
cli-spec entry names
Error message
cli-spec entry names {bin:?} in format {format:?} What it means
When completion_script iterates cli-spec sources, each entry's generated binary name and format (e.g. `fish`, `bash`) must both be plain file names because they are used to build cache/resource paths. If a cli-spec entry yields a bin or format containing path components or traversal characters, mise throws this before running the tool to generate the spec.
Solutions
- Fix the cli-spec entry in the tool's packslip manifest so the bin and format placeholders resolve to bare names (`bash`, `fish`).
- Check the tool's completion command for arguments emitting path-like values and strip them to file names in the spec template.
- Report/upgrade the packslip upstream if the release manifest is wrong.
- As a workaround, generate the completion manually with the tool's own completion command.
Example fix
// before
[resources.cli-spec]
command = "mytool completions --out dir/{format}"
// after
[resources.cli-spec]
command = "mytool completions --format {format}" Defensive patterns
Strategy: validation
Validate before calling
fn is_safe_component(s: &str) -> bool { !s.is_empty() && s != "." && s != ".." && !s.contains('/') && !s.contains('\\') }
assert!(is_safe_component(bin) && is_safe_component(format)); Type guard
fn is_plain_file_name(s: &str) -> bool { !s.is_empty() && !s.contains('/') && !s.contains('\\') && s != "." && s != ".." } Prevention
- Keep cli-spec command templates free of directory components in bin/format placeholders
- Test packslip manifests across shells before publishing releases
- Strip paths from tool completion output before templating into cli-spec
When it happens
Trigger: A packslip cli-spec entry whose completion command produces a bin name or format with `/`, `.`, `..` (e.g. the spec command template or `--format` placeholder interpolation yields `sub/dir` or an absolute path).
Common situations: Malformed cli-spec in the packslip manifest (command template inserting directories); upstream changed its completion command output naming; a spec command flag value containing slashes interpolated into bin/format.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- invalid completion cache identity
- has no executable called
- mise cannot derive completions from a
- is not an active, installed tool or one of their executables
- resource command produced no output
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/7a4758649a4e2db7.
Report an issue: GitHub.
Appendix: source
Thrown at src/packslip.rs:1539
let attempt = match source {
CompletionSource::File(path) => file::read_to_string(&path),
CompletionSource::Spec { format, bin, path } => {
derive_from_spec(&format, &bin, &path, shell)
}
CompletionSource::Exec(argv, env) => run_tool(config, &backend, &tv, &argv, &env).await,
CompletionSource::SpecExec {
format,
bin,
argv,
env,
} => {
// Any failure here is one more reason to try the next source,
// not the end of the search. The spec is kept in the install:
// a script derived from it names the file at completion time,
// so it has to outlive this command.
async {
if !file::is_plain_file_name(&bin) || !file::is_plain_file_name(&format) {
bail!("cli-spec entry names {bin:?} in format {format:?}");
}
let spec = run_tool(config, &backend, &tv, &argv, &env).await?;
// A spec generated for one shell, as `{shell}` in the
// command allows, is not the spec for another, and two
// shells generating at once must not read each other's
// half-written file. `shell` is a plain file name: the
// cache path above refuses anything else.
let dir = install_path.join(RESOURCES_DIR).join("specs").join(shell);
file::create_dir_all(&dir)?;
let path = dir.join(format!("{bin}.{format}"));
file::write_atomic(&path, &spec)?;
derive_from_spec(&format, &bin, &path, shell)
}
.await
}
};
match attempt {
Ok(script) if script.trim().is_empty() => {View on GitHub (pinned to 533346cc37)