elastic/elasticsearch · error · InvalidUserDataException
${snippet}: wasn't first. TESTSETUP can only be used in the
Error message
${snippet}: wasn't first. TESTSETUP can only be used in the first snippet of a document. What it means
testSetup handles // TESTSETUP snippets. It checks if lastDocsPath == snippet.path() (reference equality on Path — effectively 'has a snippet from this file already been processed?'). If a snippet from the same file was already seen, this TESTSETUP is not the first snippet in the document, which violates the rule that setup must open the file. lastDocsPath is set only after setupCurrent processes the first snippet, so equality means an earlier snippet in this file preceded the setup.
Source
Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/doc/RestTestsFromDocSnippetTask.java:453
} else {
dest = dest.getParent().resolve(fileName.replace(".asciidoc", ".yml").replace(".mdx", ".yml"));
}
// Now setup the writer
try {
Files.createDirectories(dest.getParent());
current = new PrintWriter(dest.toFile(), StandardCharsets.UTF_8);
return current;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void testSetup(Snippet snippet) {
if (lastDocsPath == snippet.path()) {
throw new InvalidUserDataException(
snippet + ": wasn't first. TESTSETUP can only be used in the first snippet of a document."
);
}
setupCurrent(snippet);
current.println("---");
current.println("setup:");
if (snippet.setup() != null) {
setup(snippet);
}
body(snippet, true);
}
private void setup(final Snippet snippet) {
// insert a setup defined outside of the docs
for (final String name : snippet.setup().split(",")) {
final String setup = getSetups().get(name);
if (setup == null) {
throw new InvalidUserDataException("Couldn't find named setup " + name + " for " + snippet);View on GitHub (pinned to db6a809a66)
Solutions
- Move the // TESTSETUP snippet to the very top of the document, before any other snippet.
- If the setup logic is needed mid-file, convert it to a named setup registered in the build and reference it via // SETUP[name] on individual snippets instead.
Example fix
// before — TESTSETUP after a request
// CONSOLE
GET /_search
// TESTSETUP
POST /idx/_doc
{ "a": 1 }
// after — TESTSETUP first
// TESTSETUP
POST /idx/_doc
{ "a": 1 }
// CONSOLE
GET /_search Defensive patterns
Strategy: validation
Validate before calling
// Ensure // TESTSETUP is the first snippet in its file
void checkTestSetupFirst(List<SnippetInfo> fileSnippets, String filePath) {
for (int i = 0; i < fileSnippets.size(); i++) {
if (fileSnippets.get(i).testSetup && i != 0) {
throw new IllegalStateException(
"TESTSETUP in " + filePath + " must be the first snippet (at index " + i + ")");
}
}
} Prevention
- Always put // TESTSETUP at the very top of the document file.
- If setup logic is needed mid-file, use named setups (// SETUP[name]) on individual snippets instead.
- After reordering snippets, verify TESTSETUP is still first.
When it happens
Trigger: A // TESTSETUP snippet appears after another snippet (console request, response, etc.) in the same document file.
Common situations: Moving a TESTSETUP block down in the file below example requests; adding an introductory console example before the setup.
Related errors
- ${snippet}: No paired previous test
- // TEST[continued] cannot immediately follow // TESTSETUP: $
- Extra content ${message} ('${cutOutNoNl}') matching [${patte
- Didn't match ${pattern}: ${content}
- Invalid block quote starting at ${start} in: ${body}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/a4c8b0178da17abe.
Report an issue: GitHub.