{"record":{"id":"7a4758649a4e2db7","repo":"jdx/mise","slug":"cli-spec-entry-names-bin-in-format-format","errorCode":null,"errorMessage":"cli-spec entry names {bin:?} in format {format:?}","messagePattern":"cli-spec entry names (.+?) in format (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/packslip.rs","lineNumber":1234,"sourceCode":"        let attempt = match source {\n            CompletionSource::File(path) => file::read_to_string(&path),\n            CompletionSource::Spec { format, bin, path } => {\n                derive_from_spec(&format, &bin, &path, shell)\n            }\n            CompletionSource::Exec(argv, env) => run_tool(config, &backend, &tv, &argv, &env).await,\n            CompletionSource::SpecExec {\n                format,\n                bin,\n                argv,\n                env,\n            } => {\n                // Any failure here is one more reason to try the next source,\n                // not the end of the search. The spec is kept in the install:\n                // a script derived from it names the file at completion time,\n                // so it has to outlive this command.\n                async {\n                    if !file::is_plain_file_name(&bin) || !file::is_plain_file_name(&format) {\n                        bail!(\"cli-spec entry names {bin:?} in format {format:?}\");\n                    }\n                    let spec = run_tool(config, &backend, &tv, &argv, &env).await?;\n                    // A spec generated for one shell, as `{shell}` in the\n                    // command allows, is not the spec for another, and two\n                    // shells generating at once must not read each other's\n                    // half-written file. `shell` is a plain file name: the\n                    // cache path above refuses anything else.\n                    let dir = install_path.join(RESOURCES_DIR).join(\"specs\").join(shell);\n                    file::create_dir_all(&dir)?;\n                    let path = dir.join(format!(\"{bin}.{format}\"));\n                    file::write_atomic(&path, &spec)?;\n                    derive_from_spec(&format, &bin, &path, shell)\n                }\n                .await\n            }\n        };\n        match attempt {\n            Ok(script) if script.trim().is_empty() => {","sourceCodeStart":1216,"sourceCodeEnd":1252,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/packslip.rs#L1216-L1252","documentation":"For SpecExec completion sources, the packslip names the binary and spec format whose generated spec will be cached as <bin>.<format>. mise validates both are plain file names before using them in the cache path; if either contains path separators or unsafe components, it refuses, since a non-plain name could escape the specs directory or collide across shells.","triggerScenarios":"Running `mise completion <shell> --tool <tool>` where the packslip's cli-spec exec entry declares a bin or format string that is not a plain file name (contains '/', '\\\\', '..', or is empty).","commonSituations":"A mis-authored packslip cli-spec entry with a path-qualified bin name (e.g. \"bin/tool\") or an unusual format string; a corrupted or hand-edited packslip statement.","solutions":["Inspect the packslip statement's cli-spec entry and set bin to the bare executable name and format to a bare token like \"usage\".","Reinstall from a corrected packslip release: `mise packslip forget <project> && mise install <tool>`.","If the statement was hand-edited, restore it by reinstalling the tool version."],"exampleFix":"// packslip cli-spec — before\n[[resource]]\ntype = \"cli-spec\"\nbin = \"bin/mytool\"\nformat = \"usage\"\n// after\n[[resource]]\ntype = \"cli-spec\"\nbin = \"mytool\"\nformat = \"usage\"","handlingStrategy":"validation","validationCode":"fn plain_name(s: &str) -> bool { !s.is_empty() && !s.contains(['/', '\\\\']) && s != \".\" && s != \"..\" }\n// for each cli-spec entry in the statement:\nassert!(plain_name(entry.bin) && plain_name(entry.format));","typeGuard":"fn valid_spec_entry(bin: &str, format: &str) -> bool { is_plain_file_name(bin) && is_plain_file_name(format) }","tryCatchPattern":"if !valid_spec_entry(&entry.bin, &entry.format) {\n    eprintln!(\"skipping malformed cli-spec entry {:?}/{:?}\", entry.bin, entry.format);\n    // try the next completion source\n}","preventionTips":["Author packslip cli-spec entries with bare executable names and bare format tokens.","Never hand-edit packslip statements; reinstall from the published packslip instead.","Validate packslips with mise's packslip tooling before release."],"tags":["completions","packslip","validation","path-safety"],"backgroundTag":"invalid-identifier-format","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}