jdx/mise · error

failed to uninstall {tv}

Error message

failed to uninstall {tv}

What it means

`mise uninstall` wraps any failure from a tool backend's uninstall routine with the context "failed to uninstall {tv}" (the tool@version being removed). The backend error is preserved as the wrapped cause, so this is a contextual wrapper, not a root cause. It is returned (not just warned), so the CLI exits non-zero.

Source

Thrown at src/cli/uninstall.rs:125

        };
        for (plugin, tv) in to_remove {
            let key = format!("{}@{}", tv.ba().short, tv.version);
            let tool = progress
                .as_ref()
                .and_then(|progress| progress.start_tool(&key));
            let pr = match &tool {
                Some(tool) => tool.reporter(),
                None => mpr.add(&tv.style()),
            };
            let result = plugin
                .uninstall_version(&config, &tv, pr.as_ref(), self.is_dry_run())
                .await;
            if let Some(tool) = &tool {
                tool.complete(result.as_ref().err().map(|e| e.to_string()).as_deref());
            }
            if let Err(err) = result {
                error!("{err}");
                return Err(eyre!(err).wrap_err(format!("failed to uninstall {tv}")));
            }
            if self.is_dry_run() {
                pr.finish_with_message("uninstalled (dry-run)".into());
            } else {
                if let Err(err) = crate::tool_purgatory::forget_path(&tv.install_path()) {
                    warn!("failed to clear tool purgatory entry: {err:#}");
                }
                if tool.is_none() {
                    pr.finish_with_message("uninstalled".into());
                }
            }
        }
        if let Some(progress) = progress.as_mut() {
            progress.finish(vec![]);
        }

        if self.is_dry_run() {
            if self.dry_run_code && has_work {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Read the wrapped cause (full `{err:#}` chain via verbose output) to find the backend failure, then fix that specific condition.
  2. Close processes using the install path (or stop AV scanning) and retry the uninstall.
  3. If the directory is already gone, remove stale metadata (the version dir under mise's installs path) or reinstall then uninstall.
  4. Use `mise uninstall --dry-run` first to see what will be removed.

Example fix

// before
mise uninstall node@20.0.0   # fails: install dir locked
// after
pkill -f node; mise uninstall node@20.0.0
Defensive patterns

Strategy: try-catch

Validate before calling

mise ls <tool>  # confirm the version exists before uninstalling
mise uninstall <tool>@<version> --dry-run

Try / catch

match mise_uninstall(tool, version) {
    Err(e) => {
        eprintln!("uninstall failed: {e:#}"); // inspect full chain for root cause
        // e.g. kill locking processes, then retry once
    }
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Running `mise uninstall <tool>@<version>` when the backend's uninstall call returns Err — e.g. the install directory is missing but tracked, files are held open / permission-denied on Windows, or the underlying remove fails mid-way. The `tool.complete(...)` hook is invoked with the error string before wrapping.

Common situations: Manually deleted install dirs leaving purgatory/metadata stale; another process (editor, AV, running tool binary) locking files; partial installs; removing a version while a shim or task still uses it.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/a9d32d0dc8cf8223. Report an issue: GitHub.