elastic/elasticsearch · error · IllegalStateException
Cannot add nodes to test cluster after is has been frozen
Error message
Cannot add nodes to test cluster after is has been frozen
What it means
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.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchCluster.java:431
public void jvmArgs(String... values) {
nodes.all(each -> each.jvmArgs(values));
}
@Internal
public boolean isPreserveDataDir() {
return nodes.stream().anyMatch(node -> node.isPreserveDataDir());
}
@Override
public void setPreserveDataDir(boolean preserveDataDir) {
nodes.all(each -> each.setPreserveDataDir(preserveDataDir));
}
@Override
public void freeze() {
nodes.forEach(ElasticsearchNode::freeze);
configurationFrozen.set(true);
nodes.whenObjectAdded(node -> { throw new IllegalStateException("Cannot add nodes to test cluster after is has been frozen"); });
}
private void checkFrozen() {
if (configurationFrozen.get()) {
throw new IllegalStateException("Configuration for " + this + " can not be altered, already locked");
}
}
@Override
public void start() {
commonNodeConfig();
nodes.forEach(ElasticsearchNode::start);
}
private void commonNodeConfig() {
final String nodeNames;
if (nodes.stream().map(ElasticsearchNode::getName).anyMatch(name -> name == null)) {
nodeNames = null;View on GitHub (pinned to db6a809a66)
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.
Example fix
// before
tasks.named('integTest').configure {
doFirst { testClusters.cluster.numberOfNodes = 5 } // freeze already ran
}
// after
testClusters {
cluster { numberOfNodes = 5 }
} Defensive patterns
Strategy: validation
Validate before calling
// Guard: only mutate in the configuration phase
if (cluster.getState().equals('EXECUTED') /* or check taskGraph */) {
throw new IllegalStateException("Cluster frozen; cannot add nodes");
}
// Prefer: do all mutation in testClusters { } blocks, not in task actions. Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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().
Related errors
- Configuration for {} can not be altered, already locked
- Number of nodes should be >= 1 but was {} for {}
- Cannot shrink {} to have {} nodes as it already has {}
- Can not start {}, missing: {}
- Ran out of nodes to take to the next version
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/14fadcbc8c3875e6.
Report an issue: GitHub.