elastic/elasticsearch · error · IllegalStateException
Configuration for {} can not be altered, already locked
Error message
Configuration for {} can not be altered, already locked What it means
Thrown by checkFrozen() when any configuration-mutating method is called after configurationFrozen AtomicBoolean has been set to true. The plugin freezes the node config once the cluster has started building/starting to prevent races where settings change mid-run.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:1551
gcLogSub = new ReplacementKey("logs/gc.log", "gc.log");
}
expansions.put(gcLogSub, confPathLogs.resolve("gc.log").toString());
ReplacementKey errorFileSub;
if (version.before("8.19.0") && version.getMajor() >= 7) {
errorFileSub = new ReplacementKey("-XX:ErrorFile=logs/hs_err_pid%p.log", null);
} else {
// temporarily check the old substitution first so both old and new work during backport
errorFileSub = new ReplacementKey("-XX:ErrorFile=logs/hs_err_pid%p.log", "-XX:ErrorFile=hs_err_pid%p.log");
}
expansions.put(errorFileSub, "-XX:ErrorFile=" + confPathLogs.resolve("hs_err_pid%p.log"));
return expansions;
}
private void checkFrozen() {
if (configurationFrozen.get()) {
throw new IllegalStateException("Configuration for " + this + " can not be altered, already locked");
}
}
private List<String> getTransportPortInternal() {
try {
return readPortsFile(transportPortFile);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read transport ports file: " + transportPortFile + " for " + this, e);
}
}
private List<String> getHttpPortInternal() {
try {
return readPortsFile(httpPortsFile);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read http ports file: " + httpPortsFile + " for " + this, e);
}
}View on GitHub (pinned to db6a809a66)
Solutions
- Move all node.configuration calls into the Gradle configuration phase (the body of the task definition or a configuration block), not into an action that runs at execution time.
- Use additionalSettings on RunTask (applied before freeze) rather than mutating nodes directly inside an action.
- Check the stack trace to find which setter was called after freeze and reorder it.
Example fix
// before: mutates after start
task myRun(type: RunTask) {
doFirst { cluster.getNodes().each { it.setting('x', 'y') } } // throws
}
// after: configure in configuration phase
task myRun(type: RunTask) {
additionalSettings = ['x':'y']
} Defensive patterns
Strategy: validation
Validate before calling
if (configurationFrozen.get()) {
throw new IllegalStateException("Config already frozen; mutate in configuration phase");
} Prevention
- Configure nodes in the Gradle configuration phase, not in doFirst/doLast actions.
- Use RunTask's additionalSettings rather than direct node mutation.
- Check the stack trace to find the late setter and reorder it.
When it happens
Trigger: A test or build script calls node.setting(...), setHttpPort(...), setDataPath(...), keystorePassword(...), or similar setters after the node has already started (or after the RunTask has begun its setup loop and frozen the config).
Common situations: A doFirst / doLast block in build.gradle that mutates the node at the wrong lifecycle phase; calling additionalSettings.forEach(node::setting) after cluster start; a custom task subclassing RunTask that configures nodes too late.
Related errors
- MockApmServer already started
- MockApmServer not started
- distribution type [${typeName}] for elasticsearch distributi
- Cannot add nodes to test cluster after is has been frozen
- Configuration for {} can not be altered, already locked
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/19cc3518349f566e.
Report an issue: GitHub.