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

  1. 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.
  2. Use additionalSettings on RunTask (applied before freeze) rather than mutating nodes directly inside an action.
  3. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/19cc3518349f566e. Report an issue: GitHub.