elastic/elasticsearch · error · UncheckedIOException
Can't append roles file {} to {}
Error message
Can't append roles file {} to {} What it means
Thrown by configureSecurity() when reading a roles file or appending it to <config>/roles.yml raises an IOException, wrapped as UncheckedIOException with source and destination paths. The existence check passed, so this is a low-level FS failure during read or append.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:688
rolesFile(getBuildPluginFile("/roles.yml"));
}
if (roleFiles.isEmpty() == false) {
logToProcessStdout("Setting up roles.yml");
Path dst = configFile.getParent().resolve("roles.yml");
roleFiles.forEach(from -> {
if (Files.exists(from.toPath()) == false) {
throw new TestClustersException(
"Can't create roles.yml config file from " + from + " for " + this + " as it does not exist"
);
}
try {
final Path source = from.toPath();
final String content = Files.readString(source, StandardCharsets.UTF_8);
Files.writeString(dst, content + System.lineSeparator(), StandardCharsets.UTF_8, StandardOpenOption.APPEND);
LOGGER.info("Appended roles file {} to {}", source, dst);
} catch (IOException e) {
throw new UncheckedIOException("Can't append roles file " + from + " to " + dst, e);
}
});
}
}
private void installModules() {
logToProcessStdout("Installing " + modules.size() + " modules");
for (Provider<File> module : modules) {
Path destination = getDistroDir().resolve("modules")
.resolve(module.get().getName().replace(".zip", "").replace("-" + getVersion(), "").replace("-SNAPSHOT", ""));
// only install modules that are not already bundled with the integ-test distribution
if (Files.exists(destination) == false) {
fileSystemOperations.copy(spec -> {
if (module.get().getName().toLowerCase().endsWith(".zip")) {
spec.from(archiveOperations.zipTree(module));
} else if (module.get().isDirectory()) {
spec.from(module);
} else {View on GitHub (pinned to db6a809a66)
Solutions
- Inspect the cause IOException for the precise failure (read vs write).
- Clean build/testclusters to remove stale read-only roles.yml.
- Free disk / fix permissions on the config dir.
- Ensure roles files are not shared across nodes that start concurrently; give each node its own config dir.
Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure dst parent exists and is writable, and roles are not shared across nodes
Path rolesDst = node.getConfigFile().getParent().resolve("roles.yml");
if (Files.exists(rolesDst.getParent()) && !Files.isWritable(rolesDst.getParent())) {
throw new IllegalStateException("roles.yml dir not writable: " + rolesDst.getParent());
} Try / catch
try {
node.start();
} catch (UncheckedIOException e) {
if (e.getMessage().startsWith("Can't append roles file")) {
throw new IllegalStateException("Roles append FS failure: " + e.getCause(), e);
}
throw e;
} Prevention
- Give each concurrent node its own config dir to avoid roles.yml append races.
- Free disk; fix permissions on the config dir.
- Disable AV locking for the build dir on Windows.
When it happens
Trigger: Files.readString on the source fails (e.g. permission, file deleted between the exists check and read), or Files.writeString(dst, ..., APPEND) fails because dst's parent is missing, read-only, locked, or the disk is full. Also when two roles files race to append to the same dst across nodes sharing a config dir.
Common situations: Disk full. Antivirus locking roles.yml on Windows. Permission residue. Misconfigured config dir. Concurrent nodes writing a shared config path.
Related errors
- Can't create roles.yml config file from {} for {} as it does
- Failed to write unicast_hosts for {}
- Failed to create working directory for {}, with: {}
- supplied keystore file {} does not exist, require for {}
- Can't create extra config file for
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/b069c7435f783f4a.
Report an issue: GitHub.