{"record":{"id":"e487fba224a57868","repo":"jdx/mise","slug":"package-plugin-does-not-support-uninstall-ad-e487fb","errorCode":null,"errorMessage":"package plugin '{}' does not support uninstall; add hooks/package_uninstall.lua","messagePattern":"package plugin '(.+?)' does not support uninstall; add hooks/package_uninstall\\.lua","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/plugin.rs","lineNumber":444,"sourceCode":"        let before = self.installed(&requests).await?;\n        let present = before\n            .iter()\n            .filter_map(|status| {\n                Self::status_version(status).map(|version| PackageRequest {\n                    name: status.request.name.clone(),\n                    version: Some(version),\n                    tap_url: None,\n                    desired: crate::system::packages::PackageDesiredState::Present,\n                })\n            })\n            .collect::<Vec<_>>();\n        Self::reconcile_missing_ownership(&mut state, &before);\n        self.save_state(&state)?;\n        if present.is_empty() {\n            return Ok(0);\n        }\n        if !self.supports_uninstall() {\n            bail!(\n                \"package plugin '{}' does not support uninstall; add hooks/package_uninstall.lua\",\n                self.name\n            );\n        }\n\n        let action_result = self.uninstall_action(&present).await;\n        let after = self.installed(&present).await;\n        let after = match after {\n            Ok(after) => after,\n            Err(status_err) => {\n                if let Err(action_err) = action_result {\n                    warn!(\n                        \"{}: failed to verify uninstall after error: {status_err:#}\",\n                        self.name\n                    );\n                    return Err(action_err);\n                }\n                return Err(status_err);","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/plugin.rs#L426-L462","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add a `hooks/package_uninstall.lua` hook to the plugin implementing package removal for its manager.","If the plugin is intentionally install-only, remove/uninstall the packages with the underlying package manager directly instead of via mise.","Check the plugin's directory layout — the hook must be exactly at `hooks/package_uninstall.lua` relative to the plugin root.","Switch to a plugin variant that supports uninstall if you cannot modify the plugin."],"exampleFix":"# plugin layout — before\nmyplugin/\n  hooks/\n    package_install.lua\n\n# after\nmyplugin/\n  hooks/\n    package_install.lua\n    package_uninstall.lua   # required for uninstall/prune support","handlingStrategy":"validation","validationCode":"// Check that the plugin supports uninstall before running prune/uninstall\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nconst pluginRoot = resolvePluginRoot(pluginName); // e.g. from mise plugin list\nif (!existsSync(join(pluginRoot, 'hooks', 'package_uninstall.lua'))) {\n  console.warn(`Plugin '${pluginName}' is install-only; uninstall its packages with the underlying manager directly`);\n}","typeGuard":"const supportsUninstall = (root) => existsSync(join(root, 'hooks', 'package_uninstall.lua'));","tryCatchPattern":"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; }","preventionTips":["When authoring a package plugin, always ship hooks/package_uninstall.lua alongside package_install.lua.","Treat install-only plugins as one-way; document that in the plugin README before teams adopt it.","Verify plugin directory layout after upgrades/renames — hook filenames are exact.","Prefer plugins from the mise plugin registry that advertise uninstall support if you need prune flows."],"tags":["package-plugin","uninstall","missing-hook","lua"],"backgroundTag":"operation-not-supported","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"}