elastic/elasticsearch · error · InvalidUserDataException

TESTRESPONSE not paired with a snippet at

Error message

TESTRESPONSE not paired with a snippet at 

What it means

Thrown by SnippetParser.testResponseHandled() when a line matching the TESTRESPONSE regex is encountered but there is no currently-open snippet (snippetBuilder == null). TESTRESPONSE markers are response assertions that must follow a CONSOLE snippet; a TESTRESPONSE with no preceding snippet has nothing to test against. Note the message ends with 'at ' but no location is appended, so the file:line comes only from the outer SnippetParserException wrapper (error 208).

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/doc/SnippetParser.java:146

    protected SnippetBuilder newSnippetBuilder() {
        snippetBuilder = new SnippetBuilder().withPath(currentPath);
        return snippetBuilder;
    }

    void fileParsingFinished(List<Snippet> snippets) {
        if (snippetBuilder != null) {
            snippets.add(snippetBuilder.build());
            snippetBuilder = null;
        }
    }

    protected abstract void parseLine(List<Snippet> snippets, int lineNumber, String line);

    boolean testResponseHandled(String line, SnippetBuilder snippetBuilder) {
        Matcher matcher = testResponsePattern().matcher(line);
        if (matcher.matches()) {
            if (snippetBuilder == null) {
                throw new InvalidUserDataException("TESTRESPONSE not paired with a snippet at ");
            }
            snippetBuilder.withTestResponse(true);
            if (matcher.group(2) != null) {
                ParsingUtils.parse(
                    matcher.group(2),
                    "(?:" + SUBSTITUTION + "|" + NON_JSON + "|" + SKIP_REGEX + ") ?",
                    (Matcher m, Boolean last) -> {
                        if (m.group(1) != null) {
                            // TESTRESPONSE[s/adsf/jkl/]
                            snippetBuilder.withSubstitution(m.group(1), m.group(2));
                        } else if (m.group(3) != null) {
                            // TESTRESPONSE[non_json]
                            snippetBuilder.withSubstitution("^", "/");
                            snippetBuilder.withSubstitution("\n$", "\\\\s*/");
                            snippetBuilder.withSubstitution("( +)", "$1\\\\s+");
                            snippetBuilder.withSubstitution("\n", "\\\\s*\n ");
                        } else if (m.group(4) != null) {
                            // TESTRESPONSE[skip:reason]

View on GitHub (pinned to db6a809a66)

Solutions

  1. Locate the `// TESTRESPONSE` line (use the file:line from the outer SnippetParserException) and ensure it directly follows the `// CONSOLE` snippet whose response it asserts.
  2. If the CONSOLE snippet was removed intentionally, delete the orphaned TESTRESPONSE block as well.
  3. Verify there is no stray snippet-closing directive between the CONSOLE block and the TESTRESPONSE.

Example fix

// before:
// TESTRESPONSE
{ "acknowledged": true }
// (no preceding CONSOLE)
// after:
// CONSOLE
PUT /idx
// TESTRESPONSE
{ "acknowledged": true }
Defensive patterns

Strategy: validation

Validate before calling

// Before building, walk doc lines and ensure every // TESTRESPONSE has a preceding open // CONSOLE snippet.

Prevention

When it happens

Trigger: A documentation file contains a `// TESTRESPONSE[...]` line that is not preceded by a `// CONSOLE` snippet block (or the preceding snippet was already closed/finalized). The testResponsePattern matches, snippetBuilder is null, and the exception fires.

Common situations: A TESTRESPONSE block was separated from its CONSOLE snippet by editing (e.g. the CONSOLE block was deleted but the TESTRESPONSE left behind); the markers are in the wrong order; an extra blank-line or directive caused the parser to close the snippet before reaching TESTRESPONSE.

Related errors


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