{"record":{"id":"e74837fca38c38ae","repo":"elastic/elasticsearch","slug":"11","errorCode":"11","errorMessage":"Cannot remove plugins because the following are extended by other plugins:\n\t{key} used by {value}","messagePattern":"Cannot remove plugins because the following are extended by other plugins:\n\t(.+?) used by (.+?)","errorType":"exception","errorClass":"UserException","httpStatus":null,"severity":"error","filePath":"distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/RemovePluginAction.java","lineNumber":118,"sourceCode":"                    String pluginId = plugin.getId();\n                    if (extendedPlugin.equals(pluginId)) {\n                        usedBy.computeIfAbsent(entry.getKey(), (_key -> new ArrayList<>())).add(pluginId);\n                    }\n                }\n            }\n        }\n        if (usedBy.isEmpty()) {\n            return;\n        }\n\n        final StringJoiner message = new StringJoiner(\"\\n\");\n        message.add(\"Cannot remove plugins because the following are extended by other plugins:\");\n        usedBy.forEach((key, value) -> {\n            String s = \"\\t\" + key + \" used by \" + value;\n            message.add(s);\n        });\n\n        throw new UserException(PLUGIN_STILL_USED, message.toString());\n    }\n\n    private void checkCanRemove(InstallablePlugin plugin) throws UserException {\n        String pluginId = plugin.getId();\n\n        final Path pluginDir = env.pluginsDir().resolve(pluginId);\n        final Path pluginConfigDir = env.configDir().resolve(pluginId);\n        final Path removing = env.pluginsDir().resolve(\".removing-\" + pluginId);\n\n        /*\n         * If the plugin does not exist and the plugin config does not exist, fail to the user that the plugin is not found, unless there's\n         * a marker file left from a previously failed attempt in which case we proceed to clean up the marker file. Or, if the plugin does\n         * not exist, the plugin config does, and we are not purging, again fail to the user that the plugin is not found.\n         */\n        if ((Files.exists(pluginDir) == false && Files.exists(pluginConfigDir) == false && Files.exists(removing) == false)\n            || (Files.exists(pluginDir) == false && Files.exists(pluginConfigDir) && this.purge == false)) {\n\n            if (PLUGINS_CONVERTED_TO_MODULES.contains(pluginId)) {","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/RemovePluginAction.java#L100-L136","documentation":"Thrown by RemovePluginAction.ensurePluginsNotUsedByOtherPlugins when one or more of the plugins requested for removal are still extended/depended-on by other installed plugins. Exit code PLUGIN_STILL_USED (11) — a custom code specific to this case. The message lists every dependency edge (`key used by value`).","triggerScenarios":"ensurePluginsNotUsedByOtherPlugins builds a `usedBy` map of plugin->dependents from the installed set; if it is non-empty, a multi-line UserException is thrown before any per-plugin checkCanRemove runs. Removing a base plugin while an extending plugin still references it triggers this.","commonSituations":"Trying to remove `analysis-icu` while a custom plugin extends it; removing a parent/joint plugin that another depends on; batch removal that lists only the dependency and not the dependent.","solutions":["Remove the dependent plugins first, then the base — read the `used by` list in the message.","List both the base and all its dependents in a single `remove` invocation.","Re-run `elasticsearch-plugin list` and trace which plugin extends which before retrying."],"exampleFix":"# before (fails):\nbin/elasticsearch-plugin remove analysis-icu\n# error: analysis-icu used by my-analysis-ext\n# after (remove dependent first or together):\nbin/elasticsearch-plugin remove my-analysis-ext analysis-icu","handlingStrategy":"try-catch","validationCode":"// Pre-check dependents before removal.\nMap<String, String> usedBy = buildUsedByMap(installedPlugins);\nfor (String id : idsToRemove) {\n    if (usedBy.containsKey(id)) {\n        throw new IllegalStateException(id + \" is used by \" + usedBy.get(id));\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    removeAction.execute(plugins);\n} catch (UserException e) {\n    if (e.exitCode == RemovePluginAction.PLUGIN_STILL_USED) {\n        // parse `used by` and offer to remove dependents too\n        System.err.println(\"Remove dependents first: \" + e.getMessage());\n    }\n    throw e;\n}","preventionTips":["Maintain a dependency map of installed plugins and remove leaf-first.","When scripting removals, include a plugin's dependents in the same batch.","Surface the `used by` chain to operators before they schedule a removal."],"tags":["plugin-remove","dependency","plugin-cli"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}