jdx/mise · error

's bin/install exited successfully but installed nothing…

Error message

{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

What it means

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.

Solutions

  1. Fix the plugin's bin/install to copy/build its artifacts into $ASDF_INSTALL_PATH (respect the env var; don't hardcode paths).
  2. Make bin/install exit non-zero when the download or build fails so mise never sees a successful no-op.
  3. Test the plugin with the version in question (e.g. run bin/install manually with ASDF_INSTALL_PATH set) and inspect what it produces.
  4. Check the plugin repository for a newer/patched release fixing the version's install script.

Example fix

# before (plugin bin/install)
curl -fsSL "$download_url" -o /tmp/tool.tar.gz || true
tar -xzf /tmp/tool.tar.gz

# after
mkdir -p "$ASDF_INSTALL_PATH"
curl -fsSL "$download_url" -o /tmp/tool.tar.gz
tar -xzf /tmp/tool.tar.gz -C "$ASDF_INSTALL_PATH" --strip-components=1
Defensive patterns

Strategy: validation

Validate before calling

# before trusting a plugin
ASDF_INSTALL_PATH=$(mktemp -d) "$plugin_dir/bin/install" || exit 1
[ -z "$(ls -A "$ASDF_INSTALL_PATH")" ] && { echo 'plugin installs nothing'; exit 1; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/68715511d7b2bd4f. Report an issue: GitHub.

Appendix: source

Thrown at src/backend/asdf.rs:549

/// Verifies that `bin/install` actually put something in `$ASDF_INSTALL_PATH`.
///
/// `bin/install` is a plugin-supplied shell script, and one that installs nothing can still exit 0
/// — a missing `set -e`, a build that fails inside a pipeline, a download that 404s. mise would
/// otherwise report `✓ installed`, record the version in install state, and keep resolving to an
/// empty directory afterwards (#5288).
///
/// Emptiness is exact rather than a guess: `Backend::create_install_dirs` recreates the install
/// path immediately before the script runs, and the `incomplete` marker lives under the cache dir,
/// so anything present afterwards came from the plugin. It is also all that can be checked — asdf
/// plugins install into `bin/`, `libexec/`, an unpacked tarball root, or wherever the tool puts
/// things, so a stricter test (spm requires an executable in `bin/`) would reject working plugins.
///
/// There is no `system` case to exclude: anything reaching here has already been through
/// `script_man_for_tv`, which panics for `ToolRequest::System`.
fn verify_install_script_output(tool: &str, install_path: &Path) -> Result<()> {
    if file::ls(install_path)?.is_empty() {
        bail!(
            "{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",
            file::display_path(install_path)
        );
    }
    Ok(())
}

impl Debug for AsdfBackend {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AsdfPlugin")
            .field("name", &self.name)
            .field("plugin_path", &self.plugin_path)
            .field("cache_path", &self.ba.cache_path)
            .field("downloads_path", &self.ba.downloads_path)
            .field("installs_path", &self.ba.installs_path)
            .field("repo_url", &self.repo_url)
            .finish()
    }

View on GitHub (pinned to 533346cc37)