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
- Locate the `// TESTRESPONSE` line (use the file:line from the outer SnippetParserException) and ensure it directly follows the `// CONSOLE` snippet whose response it asserts.
- If the CONSOLE snippet was removed intentionally, delete the orphaned TESTRESPONSE block as well.
- 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
- Always pair `// TESTRESPONSE` immediately after the `// CONSOLE` snippet it tests.
- When deleting a CONSOLE block, delete its TESTRESPONSE too.
- Avoid inserting directives between the snippet and its TESTRESPONSE.
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
- Unexpected unconverted snippets: {foundButNotListed}
- Couldn't find the corresponding mdx file for {asciidocFileAb
- Yaml rest specs ({asciidocFile} and {mdxFile}) are not equal
- Yaml rest specs ({asciidocFile} and {mdxFile}) are not equal
- {name}: Snippet missing a language. This is required by Elas
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/6012a0d570237fed.
Report an issue: GitHub.