elastic/elasticsearch · warning · UncheckedIOException
Failed to tail log {}
Error message
Failed to tail log {} What it means
Thrown while reading the ES output log file (esOutputFile) in logFileContents after the cluster stops, when an IOException escapes the LineNumberReader and tailLogs is true. tailLogs is true when the caller explicitly requested the full log (e.g. on failure) or when leak-detector lines were detected. The plugin fails loudly here because losing the diagnostic log on a failing run would hide the real error.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:1156
Pair.of(
ring.getLast(), // Original, non-normalized message (so we keep the first timestamp)
ofNullable(errorsAndWarnings.get(normalizedMessage)).map(p -> p.right() + 1).orElse(1)
)
);
}
} else {
// We combine multi line log messages to make sure we never break exceptions apart
lineToAdd = ring.removeLast() + "\n" + line;
}
}
ring.add(lineToAdd);
if (ring.size() >= TAIL_LOG_MESSAGES_COUNT) {
ring.removeFirst();
}
}
} catch (IOException e) {
if (tailLogs) {
throw new UncheckedIOException("Failed to tail log " + this, e);
}
return;
}
boolean foundLeaks = false;
for (String logLine : errorsAndWarnings.keySet()) {
if (logLine.contains("ResourceLeakDetector") || logLine.contains("LeakTracker")) {
tailLogs = true;
foundLeaks = true;
break;
}
}
if (tailLogs) {
if (errorsAndWarnings.isEmpty() == false || ring.isEmpty() == false) {
LOGGER.lifecycle("\n=== {} `{}` ===", description, this);
}
if (errorsAndWarnings.isEmpty() == false) {
LOGGER.lifecycle("\n» ↓ errors and warnings from " + from + " ↓");View on GitHub (pinned to db6a809a66)
Solutions
- Check that no parallel gradle task or external process is deleting the testclusters working directory during the run.
- If on Windows, ensure antivirus exclusions for the gradle build dir; file locking by AV is a common cause.
- Run the test in isolation (--tests <one>) to rule out concurrency between clusters sharing a working dir root.
- If the file was legitimately gone, re-run: this is often transient and the log will be present next time.
Defensive patterns
Strategy: try-catch
Validate before calling
if (Files.exists(from) == false) {
LOGGER.warn("Log file {} gone before tail; skipping", from);
return;
} Try / catch
try (LineNumberReader reader = new LineNumberReader(Files.newBufferedReader(from))) {
...
} catch (IOException e) {
if (tailLogs) throw new UncheckedIOException("Failed to tail log " + this, e);
return;
} Prevention
- Do not delete the testclusters working dir while a build is running.
- On Windows, exclude the gradle build dir from antivirus on-access scanning.
- Run flaky tests in isolation to rule out concurrency on shared working dirs.
When it happens
Trigger: Files.newBufferedReader(from) or reader.readLine() throws — the log file was deleted, moved, or its permissions changed between cluster start and the post-stop log sweep. On Windows the file may still be locked by the just-killed JVM.
Common situations: A concurrent cleanup task (reaper, gradle clean) deleted the working dir while the log was being tailed; the working dir is on a tmpfs that got cleared; antivirus quarantined the log; or a previous run's file handle is still open on Windows.
Related errors
- Failed to write unicast_hosts for {}
- IO error while waiting cluster
- Failed to create working directory for {}, with: {}
- Can't create extra config file for
- Can't copy extra jar dependency {} to {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/da539fdd28824b88.
Report an issue: GitHub.