{"record":{"id":"572da79c04b41d11","repo":"elastic/elasticsearch","slug":"can-only-be-applied-to-the-root-project","errorCode":null,"errorMessage":"{} can only be applied to the root project.","messagePattern":"(.+?) can only be applied to the root project\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"build-tools/src/main/java/org/elasticsearch/gradle/testclusters/TestClustersPlugin.java","lineNumber":220,"sourceCode":"            task.setGroup(\"ES cluster formation\");\n            task.setDescription(\"Lists all ES clusters configured for this project\");\n            task.doLast(\n                (Task t) -> container.forEach(cluster -> logger.lifecycle(\"   * {}: {}\", cluster.getName(), cluster.getNumberOfNodes()))\n            );\n        });\n    }\n\n    static abstract class TestClustersHookPlugin implements Plugin<Project> {\n        @Inject\n        public abstract BuildEventsListenerRegistry getEventsListenerRegistry();\n\n        @SuppressWarnings(\"checkstyle:RedundantModifier\")\n        @Inject\n        public TestClustersHookPlugin() {}\n\n        public void apply(Project project) {\n            if (project != project.getRootProject()) {\n                throw new IllegalStateException(this.getClass().getName() + \" can only be applied to the root project.\");\n            }\n            Provider<TestClustersRegistry> registryProvider = GradleUtils.getBuildService(\n                project.getGradle().getSharedServices(),\n                REGISTRY_SERVICE_NAME\n            );\n\n            Provider<TaskEventsService> testClusterTasksService = project.getGradle()\n                .getSharedServices()\n                .registerIfAbsent(TEST_CLUSTER_TASKS_SERVICE, TaskEventsService.class, spec -> {\n                    spec.getParameters().getRegistry().set(registryProvider);\n                });\n\n            TestClustersRegistry registry = registryProvider.get();\n            // When we know what tasks will run, we claim the clusters of those task to differentiate between clusters\n            // that are defined in the build script and the ones that will actually be used in this invocation of gradle\n            // we use this information to determine when the last task that required the cluster executed so that we can\n            // terminate the cluster right away and free up resources.\n            configureClaimClustersHook(project.getGradle(), registry);","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/TestClustersPlugin.java#L202-L238","documentation":"TestClustersHookPlugin.apply() refuses to be applied to any project other than the root. This plugin registers build-wide services (TestClustersRegistry provider, TaskEventsService) via `getEventsListenerRegistry()` and the gradle shared-services API, which only make sense once at the build root. Applying it to a subproject would create duplicate/conflicting build services.","triggerScenarios":"Explicitly `apply plugin: 'elasticsearch.testclusters'` (or the hook variant) in a subproject's build.gradle, or a convention plugin that applies it unconditionally to every project including non-root ones.","commonSituations":"A convention plugin authored without a root-project guard; applying the plugin inside a `subprojects { ... }` block; manually wiring it during plugin migration.","solutions":["Apply the hook plugin ONLY in the root project's build.gradle (or via a plugin that itself checks `project == rootProject`).","If writing a convention, guard with `if (project == project.rootProject) { apply(plugin) }`.","Confirm the plugin isn't transitively pulled by another plugin applied to subprojects — check `./gradlew <sub>:dependencies` or plugin application logging."],"exampleFix":"// before (subproject build.gradle)\napply plugin: 'elasticsearch.testclusters'\n// after: move to root build.gradle, or guard\nif (project == rootProject) {\n  apply plugin: 'elasticsearch.testclusters'\n}","handlingStrategy":"validation","validationCode":"if (project != project.getRootProject()) {\n  throw new IllegalStateException(\n    \"Apply \" + pluginId + \" only in the root project, not \" + project.getPath());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Apply the testclusters hook plugin only in the root build.gradle.","In convention plugins, guard application with `project == rootProject`.","Audit `subprojects { apply ... }` blocks for plugins that are root-only."],"tags":["gradle","testclusters","plugin","multi-project","build-tools"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}