elastic/elasticsearch · error · InvalidUserDataException
Duplicated snippet name
Error message
Duplicated snippet name '${testName}': ${test} What it means
TestBuilder.names is a per-file Set of explicit snippet names. When a non-continued test snippet has a name (from // TEST[name]) and names.add returns false, the name was already registered in this file. Duplicate names break the generated YAML test file because each test is keyed by its name.
Solutions
- Rename the duplicate so each // TEST[name] in a file is unique.
- If the snippets are genuinely the same test split across continued blocks, use // TEST[continued] on the subsequent ones instead of repeating the name.
Example fix
// before — two snippets share 'search' // CONSOLE // TEST[search] GET /idx/_search // CONSOLE // TEST[search] GET /idx2/_search // after — unique names // CONSOLE // TEST[search_idx] GET /idx/_search // CONSOLE // TEST[search_idx2] GET /idx2/_search
Defensive patterns
Strategy: validation
Validate before calling
// Ensure snippet names are unique within each doc file
import java.util.HashSet;
import java.util.Set;
void checkUniqueNames(List<SnippetInfo> fileSnippets, String filePath) {
Set<String> seen = new HashSet<>();
for (SnippetInfo s : fileSnippets) {
if (s.name != null && !s.name.isBlank()) {
if (!seen.add(s.name)) {
throw new IllegalStateException(
"Duplicate snippet name '" + s.name + "' in " + filePath);
}
}
}
} Prevention
- Give every named snippet a unique name within its file.
- For multi-step tests, use // TEST[continued] on subsequent fragments instead of repeating the name.
- When copy-pasting a snippet, always rename it.
When it happens
Trigger: Two console snippets in the same doc file both carry // TEST[myTest] (or the mdx equivalent) with the same name string.
Common situations: Copy-pasting a snippet and forgetting to rename it; merging two files that happened to use the same snippet name.
Related errors
- Continued snippets can't be skipped
- Didn't match
- Extra content (' ') matching [ ]
- Invalid block quote starting at
- : No paired previous test
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/eb861f1a892cca0e.
Report an issue: GitHub.
Appendix: source
Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/doc/RestTestsFromDocSnippetTask.java:257
setupCurrent(test);
if (test.continued()) {
/* Catch some difficult to debug errors with // TEST[continued]
* and throw a helpful error message. */
if (previousTest == null || previousTest.path().equals(test.path()) == false) {
throw new InvalidUserDataException("// TEST[continued] " + "cannot be on first snippet in a file: " + test);
}
if (previousTest != null && previousTest.testSetup()) {
throw new InvalidUserDataException("// TEST[continued] " + "cannot immediately follow // TESTSETUP: " + test);
}
if (previousTest != null && previousTest.testSetup()) {
throw new InvalidUserDataException("// TEST[continued] " + "cannot immediately follow // TEARDOWN: " + test);
}
} else {
current.println("---");
if (test.name() != null && test.name().isBlank() == false) {
if (names.add(test.name()) == false) {
throw new InvalidUserDataException("Duplicated snippet name '" + test.name() + "': " + test);
}
current.println("\"" + test.name() + "\":");
} else {
current.println("\"line_" + test.start() + "\":");
}
/* The Elasticsearch test runner doesn't support quite a few
* constructs unless we output this skip. We don't know if
* we're going to use these constructs, but we might so we
* output the skip just in case. */
current.println(" - skip:");
current.println(" features:");
current.println(" - default_shards");
current.println(" - stash_in_key");
current.println(" - stash_in_path");
current.println(" - stash_path_replace");
current.println(" - warnings");
}
if (test.skip() != null) {View on GitHub (pinned to db6a809a66)