elastic/elasticsearch · error · InvalidUserDataException

Invalid block quote starting at ${start} in: ${body}

Error message

Invalid block quote starting at ${start} in:
${body}

What it means

replaceBlockQuote converts Kibana-style triple-quote ('"""') delimited strings into standard escaped JSON strings. It finds the opening '"""' and then searches for the closing '"""'; if the closing delimiter is never found (indexOf returns -1) the string is malformed and cannot be safely converted, so it throws with the byte offset of the unmatched opening quote.

Source

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

     * contain newlines and {@code "} without the normal JSON escaping.
     * This has to add it.
     */
    @PackageScope
    static String replaceBlockQuote(String body) {
        int start = body.indexOf("\"\"\"");
        if (start < 0) {
            return body;
        }
        /*
         * 1.3 is a fairly wild guess of the extra space needed to hold
         * the escaped string.
         */
        StringBuilder result = new StringBuilder((int) (body.length() * 1.3));
        int startOfNormal = 0;
        while (start >= 0) {
            int end = body.indexOf("\"\"\"", start + 3);
            if (end < 0) {
                throw new InvalidUserDataException("Invalid block quote starting at " + start + " in:\n" + body);
            }
            result.append(body.substring(startOfNormal, start));
            result.append('"');
            result.append(body.substring(start + 3, end).replace("\"", "\\\"").replace("\n", "\\n"));
            result.append('"');
            startOfNormal = end + 3;
            start = body.indexOf("\"\"\"", startOfNormal);
        }
        result.append(body.substring(startOfNormal));
        return result.toString();
    }

    private class TestBuilder {
        /**
         * These languages aren't supported by the syntax highlighter so we
         * shouldn't use them.
         */
        private static final List BAD_LANGUAGES = List.of("json", "javascript");

View on GitHub (pinned to db6a809a66)

Solutions

  1. Find the '"""' at the reported 'start' offset and add the matching closing '"""' after the string content.
  2. If block-quoting is not needed, remove the '"""' entirely and use standard JSON string escaping (\" for quotes, \n for newlines).
  3. Validate that every '"""' in the snippet body is paired by counting occurrences (must be even).

Example fix

// before — unbalanced block quote
GET /_search
"""
{ "query": { "match_all": {} } }

// after — close the block quote
GET /_search
"""
{ "query": { "match_all": {} } }
"""
Defensive patterns

Strategy: validation

Validate before calling

// Verify triple-quote delimiters are balanced before building
void checkBlockQuotes(String body, String snippetPath) {
    int count = 0, idx = -1;
    while ((idx = body.indexOf("\"\"\"", idx + 1)) != -1) count++;
    if (count % 2 != 0) {
        throw new IllegalStateException(
            "Unbalanced '\"\"\"' block quote in " + snippetPath
            + ": found " + count + " delimiters (must be even)");
    }
}

Prevention

When it happens

Trigger: A CONSOLE snippet body or a console-result response contains a '"""' block-quote opener but no corresponding closing '"""', producing an unbalanced delimiter.

Common situations: Manually editing a JSON body in a doc snippet and deleting the closing '"""' by accident, or copy-pasting only part of a block-quoted string. The start offset in the message points at the orphaned opening delimiter.

Related errors


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