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

  1. Move the // TESTSETUP snippet to the very top of the document, before any other snippet.
  2. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/a4c8b0178da17abe. Report an issue: GitHub.