langchain-ai/langchain · error · ChevronError

Unexpected EOF\nthe tag "{open_sections[-1]}" was never clos

Error message

Unexpected EOF\nthe tag "{open_sections[-1]}" was never closed\nwas opened at line {_LAST_TAG_LINE}

What it means

Raised by the Mustache tokenizer in `langchain_core.utils.mustache` at end of input when one or more sections were opened (`{{#name}}` or `{{^name}}`) but never closed before the template ended. The message names the first unclosed tag (stack top) and the line where it was opened. Rendering aborts; no partial output is produced.

Source

Thrown at libs/core/langchain_core/utils/mustache.py:322

        # Start yielding
        # Ignore literals that are empty
        if literal:
            yield ("literal", literal)

        # Ignore comments and set delimiters
        if tag_type not in {"comment", "set delimiter?"}:
            yield (tag_type, tag_key)

    # If there are any open sections when we're done
    if open_sections:
        # Then we need to complain
        msg = (
            "Unexpected EOF\n"
            f'the tag "{open_sections[-1]}" was never closed\n'
            f"was opened at line {_LAST_TAG_LINE}"
        )
        raise ChevronError(msg)


#
# Helper functions
#


def _html_escape(string: str) -> str:
    """Return the HTML-escaped string with these characters escaped: `" & < >`."""
    html_codes = {
        '"': "&quot;",
        "<": "&lt;",
        ">": "&gt;",
    }

    # & must be handled first
    string = string.replace("&", "&amp;")
    for char, code in html_codes.items():

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Go to the reported open line and add the missing `{{/name}}` at the section's end.
  2. If the message is the EOF variant but tags look balanced, look for an unmatched opener earlier — fix any name mismatch that kept a section open.
  3. Run the tokenizer over templates at load/CI time to catch unclosed sections before runtime.

Example fix

# before
template = "{{#has_docs}}Docs:"  # never closed

# after
template = "{{#has_docs}}Docs:{{/has_docs}}"
Defensive patterns

Strategy: validation

Validate before calling

import re

def all_sections_closed(template: str) -> bool:
    stack = []
    for typ, key in re.findall(r"\{\{\s*(#|\^|/)\s*(.*?)\s*\}\}", template):
        if typ in "#^":
            stack.append(key)
        elif stack:
            stack.pop()
    return not stack

Try / catch

try:
    list(tokenize(template))
except ChevronError as e:
    raise ValueError(f"invalid template: {e}") from e

Prevention

When it happens

Trigger: A template ending with `{{#items}}...` and no `{{/items}}`, parsed via `tokenize` or rendered with `MustachePlaceholderFormatter`; also when a closing tag was mistyped so it never matched (leaving the opener on the stack).

Common situations: Long prompt templates where a section closer was accidentally deleted; templates truncated by string slicing or by token-limit trimming; closers with typos (`{{#section}}` ... `{{/seciton}}`) so an earlier mismatch error is masked into this EOF error.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/3da91b000ee38463. Report an issue: GitHub.