jdx/mise · error

package plugin ' ' does not support uninstall; add…

Error message

package plugin '{}' does not support uninstall; add hooks/package_uninstall.lua

What it means

A package plugin supports uninstalling packages only if it ships a `hooks/package_uninstall.lua` hook. During `apply_prune_plan`, after computing which packages to remove, mise checks `supports_uninstall()`; if the plugin lacks that hook it aborts rather than silently leaving packages installed while claiming to prune them. The ownership state has already been updated with missing-ownership reconciliation at this point.

Solutions

  1. Add a `hooks/package_uninstall.lua` hook to the plugin implementing package removal for its manager.
  2. If the plugin is intentionally install-only, remove/uninstall the packages with the underlying package manager directly instead of via mise.
  3. Check the plugin's directory layout — the hook must be exactly at `hooks/package_uninstall.lua` relative to the plugin root.
  4. Switch to a plugin variant that supports uninstall if you cannot modify the plugin.

Example fix

# plugin layout — before
myplugin/
  hooks/
    package_install.lua

# after
myplugin/
  hooks/
    package_install.lua
    package_uninstall.lua   # required for uninstall/prune support
Defensive patterns

Strategy: validation

Validate before calling

// Check that the plugin supports uninstall before running prune/uninstall
import { existsSync } from 'node:fs';
import { join } from 'node:path';
const pluginRoot = resolvePluginRoot(pluginName); // e.g. from mise plugin list
if (!existsSync(join(pluginRoot, 'hooks', 'package_uninstall.lua'))) {
  console.warn(`Plugin '${pluginName}' is install-only; uninstall its packages with the underlying manager directly`);
}

Type guard

const supportsUninstall = (root) => existsSync(join(root, 'hooks', 'package_uninstall.lua'));

Try / catch

try { await misePkgPrune(plugin) } catch (e) { if (String(e).includes('does not support uninstall')) { console.error('Add hooks/package_uninstall.lua to the plugin or remove packages with the manager directly'); } else throw e; }

Prevention

When it happens

Trigger: Running an operation that prunes packages (`apply_prune_plan` reached with a non-empty `present` list) against a plugin that has no `hooks/package_uninstall.lua` file — e.g. `mise uninstall`/prune flows on an install-only plugin.

Common situations: Using a community or hand-written plugin that implements only package installation; an upgraded/renamed plugin directory where the uninstall hook file is absent or misnamed; calling prune/uninstall on a plugin intended to be install-only.

Related errors


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

Appendix: source

Thrown at src/system/packages/plugin.rs:444

        let before = self.installed(&requests).await?;
        let present = before
            .iter()
            .filter_map(|status| {
                Self::status_version(status).map(|version| PackageRequest {
                    name: status.request.name.clone(),
                    version: Some(version),
                    tap_url: None,
                    desired: crate::system::packages::PackageDesiredState::Present,
                })
            })
            .collect::<Vec<_>>();
        Self::reconcile_missing_ownership(&mut state, &before);
        self.save_state(&state)?;
        if present.is_empty() {
            return Ok(0);
        }
        if !self.supports_uninstall() {
            bail!(
                "package plugin '{}' does not support uninstall; add hooks/package_uninstall.lua",
                self.name
            );
        }

        let action_result = self.uninstall_action(&present).await;
        let after = self.installed(&present).await;
        let after = match after {
            Ok(after) => after,
            Err(status_err) => {
                if let Err(action_err) = action_result {
                    warn!(
                        "{}: failed to verify uninstall after error: {status_err:#}",
                        self.name
                    );
                    return Err(action_err);
                }
                return Err(status_err);

View on GitHub (pinned to 533346cc37)