elastic/elasticsearch · error · InvalidUserDataException
Duplicated snippet name '${testName}': ${test}
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.
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)
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
- Extra content ${message} ('${cutOutNoNl}') matching [${patte
- Didn't match ${pattern}: ${content}
- Invalid block quote starting at ${start} in: ${body}
- ${snippet}: No paired previous test
- ${snippet}: Result can't be first in file
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/eb861f1a892cca0e.
Report an issue: GitHub.