{"record":{"id":"14fadcbc8c3875e6","repo":"elastic/elasticsearch","slug":"cannot-add-nodes-to-test-cluster-after-is-has-been","errorCode":null,"errorMessage":"Cannot add nodes to test cluster after is has been frozen","messagePattern":"Cannot add nodes to test cluster after is has been frozen","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchCluster.java","lineNumber":431,"sourceCode":"    public void jvmArgs(String... values) {\n        nodes.all(each -> each.jvmArgs(values));\n    }\n\n    @Internal\n    public boolean isPreserveDataDir() {\n        return nodes.stream().anyMatch(node -> node.isPreserveDataDir());\n    }\n\n    @Override\n    public void setPreserveDataDir(boolean preserveDataDir) {\n        nodes.all(each -> each.setPreserveDataDir(preserveDataDir));\n    }\n\n    @Override\n    public void freeze() {\n        nodes.forEach(ElasticsearchNode::freeze);\n        configurationFrozen.set(true);\n        nodes.whenObjectAdded(node -> { throw new IllegalStateException(\"Cannot add nodes to test cluster after is has been frozen\"); });\n    }\n\n    private void checkFrozen() {\n        if (configurationFrozen.get()) {\n            throw new IllegalStateException(\"Configuration for \" + this + \" can not be altered, already locked\");\n        }\n    }\n\n    @Override\n    public void start() {\n        commonNodeConfig();\n        nodes.forEach(ElasticsearchNode::start);\n    }\n\n    private void commonNodeConfig() {\n        final String nodeNames;\n        if (nodes.stream().map(ElasticsearchNode::getName).anyMatch(name -> name == null)) {\n            nodeNames = null;","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchCluster.java#L413-L449","documentation":"Thrown from a whenObjectAdded callback registered in ElasticsearchCluster.freeze(): once a test cluster is frozen (start of execution), any addition to its nodes container triggers this. freeze() is called by the testclusters infrastructure before booting nodes, so post-freeze mutation is a programming error.","triggerScenarios":"Code adds a node (e.g. nodes.create(...) or an internal setNumberOfNodes growth) after cluster.freeze() has executed. freeze() sets configurationFrozen and installs the callback that throws.","commonSituations":"A doFirst/doLast hook or a task action that mutates testClusters after Gradle has configured and frozen them; calling cluster APIs from a thread that races with freeze().","solutions":["Perform all node configuration in the configuration phase (testClusters { ... } blocks), not in task actions.","If dynamic sizing is needed, compute it eagerly with providers so the value is fixed before freeze.","Move the mutating call earlier — before the integTest/bwc test task that triggers freeze."],"exampleFix":"// before\ntasks.named('integTest').configure {\n  doFirst { testClusters.cluster.numberOfNodes = 5 }  // freeze already ran\n}\n\n// after\ntestClusters {\n  cluster { numberOfNodes = 5 }\n}","handlingStrategy":"validation","validationCode":"// Guard: only mutate in the configuration phase\nif (cluster.getState().equals('EXECUTED') /* or check taskGraph */) {\n    throw new IllegalStateException(\"Cluster frozen; cannot add nodes\");\n}\n// Prefer: do all mutation in testClusters { } blocks, not in task actions.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never mutate testClusters inside doFirst/doLast or task actions.","Resolve all dynamic sizing via Provider/Property before freeze.","Run `./gradlew <task> --dry-run` to surface configuration-time issues separately."],"tags":["test","testclusters","lifecycle","configuration"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}