{"record":{"id":"68715511d7b2bd4f","repo":"jdx/mise","slug":"tool-s-bin-install-exited-successfully-but-insta","errorCode":null,"errorMessage":"{tool}'s bin/install exited successfully but installed nothing into {}; check that the plugin installs into $ASDF_INSTALL_PATH and that it fails when the build fails","messagePattern":"(.+?)'s bin/install exited successfully but installed nothing into (.+?); check that the plugin installs into \\$ASDF_INSTALL_PATH and that it fails when the build fails","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/backend/asdf.rs","lineNumber":549,"sourceCode":"\n/// Verifies that `bin/install` actually put something in `$ASDF_INSTALL_PATH`.\n///\n/// `bin/install` is a plugin-supplied shell script, and one that installs nothing can still exit 0\n/// — a missing `set -e`, a build that fails inside a pipeline, a download that 404s. mise would\n/// otherwise report `✓ installed`, record the version in install state, and keep resolving to an\n/// empty directory afterwards (#5288).\n///\n/// Emptiness is exact rather than a guess: `Backend::create_install_dirs` recreates the install\n/// path immediately before the script runs, and the `incomplete` marker lives under the cache dir,\n/// so anything present afterwards came from the plugin. It is also all that can be checked — asdf\n/// plugins install into `bin/`, `libexec/`, an unpacked tarball root, or wherever the tool puts\n/// things, so a stricter test (spm requires an executable in `bin/`) would reject working plugins.\n///\n/// There is no `system` case to exclude: anything reaching here has already been through\n/// `script_man_for_tv`, which panics for `ToolRequest::System`.\nfn verify_install_script_output(tool: &str, install_path: &Path) -> Result<()> {\n    if file::ls(install_path)?.is_empty() {\n        bail!(\n            \"{tool}'s bin/install exited successfully but installed nothing into {}; check that the plugin installs into $ASDF_INSTALL_PATH and that it fails when the build fails\",\n            file::display_path(install_path)\n        );\n    }\n    Ok(())\n}\n\nimpl Debug for AsdfBackend {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"AsdfPlugin\")\n            .field(\"name\", &self.name)\n            .field(\"plugin_path\", &self.plugin_path)\n            .field(\"cache_path\", &self.ba.cache_path)\n            .field(\"downloads_path\", &self.ba.downloads_path)\n            .field(\"installs_path\", &self.ba.installs_path)\n            .field(\"repo_url\", &self.repo_url)\n            .finish()\n    }","sourceCodeStart":531,"sourceCodeEnd":567,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/backend/asdf.rs#L531-L567","documentation":"After running an asdf plugin's bin/install script, mise checks the target install directory ($ASDF_INSTALL_PATH) and fails if it is empty. This catches plugins whose install script exits 0 without actually producing any files, which would otherwise silently install a broken/nonexistent tool. It exists because asdf plugins must fail loudly when their build fails.","triggerScenarios":"Calling `mise install <tool>@<version>` (install_version_ -> verify_install_script_output) for an asdf/vfox-style plugin whose bin/install script returns success but writes nothing into $ASDF_INSTALL_PATH.","commonSituations":"Writing a custom asdf plugin where the build silently no-ops (wrong download URL handled by `|| true`, build step skipped, or files written to a hardcoded path instead of $ASDF_INSTALL_PATH); upstream release asset 404s but the script masks it; plugin updated for a new version layout that no longer matches.","solutions":["Fix the plugin's bin/install to copy/build its artifacts into $ASDF_INSTALL_PATH (respect the env var; don't hardcode paths).","Make bin/install exit non-zero when the download or build fails so mise never sees a successful no-op.","Test the plugin with the version in question (e.g. run bin/install manually with ASDF_INSTALL_PATH set) and inspect what it produces.","Check the plugin repository for a newer/patched release fixing the version's install script."],"exampleFix":"# before (plugin bin/install)\ncurl -fsSL \"$download_url\" -o /tmp/tool.tar.gz || true\ntar -xzf /tmp/tool.tar.gz\n\n# after\nmkdir -p \"$ASDF_INSTALL_PATH\"\ncurl -fsSL \"$download_url\" -o /tmp/tool.tar.gz\ntar -xzf /tmp/tool.tar.gz -C \"$ASDF_INSTALL_PATH\" --strip-components=1","handlingStrategy":"validation","validationCode":"# before trusting a plugin\nASDF_INSTALL_PATH=$(mktemp -d) \"$plugin_dir/bin/install\" || exit 1\n[ -z \"$(ls -A \"$ASDF_INSTALL_PATH\")\" ] && { echo 'plugin installs nothing'; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always have bin/install write into $ASDF_INSTALL_PATH, never hardcoded paths","Never swallow download/build failures with `|| true` in install scripts","Test plugin installs for each supported version and platform before publishing"],"tags":["asdf","plugin","install","verification"],"backgroundTag":"empty-result-set","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}