{"record":{"id":"a4186197f8911f66","repo":"elastic/elasticsearch","slug":"not-a-valid-module-for","errorCode":null,"errorMessage":"Not a valid module {} for {}","messagePattern":"Not a valid module (.+?) for (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java","lineNumber":707,"sourceCode":"                }\n            });\n        }\n    }\n\n    private void installModules() {\n        logToProcessStdout(\"Installing \" + modules.size() + \" modules\");\n        for (Provider<File> module : modules) {\n            Path destination = getDistroDir().resolve(\"modules\")\n                .resolve(module.get().getName().replace(\".zip\", \"\").replace(\"-\" + getVersion(), \"\").replace(\"-SNAPSHOT\", \"\"));\n            // only install modules that are not already bundled with the integ-test distribution\n            if (Files.exists(destination) == false) {\n                fileSystemOperations.copy(spec -> {\n                    if (module.get().getName().toLowerCase().endsWith(\".zip\")) {\n                        spec.from(archiveOperations.zipTree(module));\n                    } else if (module.get().isDirectory()) {\n                        spec.from(module);\n                    } else {\n                        throw new IllegalArgumentException(\"Not a valid module \" + module + \" for \" + this);\n                    }\n                    spec.into(destination);\n                });\n            }\n        }\n    }\n\n    @Override\n    public void extraConfigFile(String destination, File from) {\n        if (destination.contains(\"..\")) {\n            throw new IllegalArgumentException(\"extra config file destination can't be relative, was \" + destination + \" for \" + this);\n        }\n        extraConfigFiles.put(destination, from);\n    }\n\n    @Override\n    public void extraConfigFile(String destination, File from, PropertyNormalization normalization) {\n        if (destination.contains(\"..\")) {","sourceCodeStart":689,"sourceCodeEnd":725,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java#L689-L725","documentation":"Thrown by installModules() when a module Provider<File> resolves to neither a .zip (case-insensitive) nor a directory. The node cannot install such an artifact into <distro>/modules and rejects it with IllegalArgumentException, signalling a configuration error in the module dependency.","triggerScenarios":"Calling module(provider) where the provider resolves to a pom, a tar, a .jar, or a missing/unrecognised artifact. Typically the wrong Gradle configuration or artifact type is wired up (e.g. a dependency declared with type 'pom' or 'tar' instead of 'zip').","commonSituations":"Maven coordinate for the module declared with the wrong type/classifier. Configuration returning the sources or pom artifact. Module project whose main output is a directory in dev mode but a zip in CI, with a configuration that returns neither. Stale artifact in cache after switching type.","solutions":["Inspect the offending module path printed in the message.","Ensure the module dependency produces a .zip (or is a directory in dev runs): use the integ-test distribution zip of the module.","Declare the dependency with the correct type, e.g. group:..., name:'my-module', version:..., type:'zip'.","Invalidate the Gradle cache for that artifact (--refresh-dependencies) if you recently changed its type."],"exampleFix":"// before: returns a jar/pom artifact\nmodule(project(':my-module').configurations.archives)\n// after: use the integ-test zip distribution of the module\nmodule(project(':my-module').ext.distZip)\n// or coordinate with explicit type\n// dependencies { dists group:'org.elasticsearch.module', name:'my-module', version:'8.15.0', ext:'zip' }","handlingStrategy":"type-guard","validationCode":"// Validate every module provider yields a zip or directory before start\nfor (Provider<File> m : node.getModules()) {\n    File f = m.get();\n    String n = f.getName().toLowerCase();\n    if (!n.endsWith(\".zip\") && !f.isDirectory()) {\n        throw new IllegalStateException(\"Module artifact is neither zip nor dir: \" + f);\n    }\n}","typeGuard":"// Only zip-producing or directory configurations are valid module inputs\nstatic Provider<File> asModuleArtifact(TaskProvider<? extends Task> zipTask) {\n    return zipTask.flatMap(t -> t.getOutputs().getFiles().getSingleFile().toPath().toFile());\n}","tryCatchPattern":null,"preventionTips":["Declare module dependencies with type:'zip' or ext:'zip'.","Use the module's integ-test-zip distribution, not its jar.","Invalidate Gradle cache after changing artifact type."],"tags":["testclusters","modules","classpath","validation","config"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}