langchain-ai/langchain · error · ChevronError

Trying to close tag "{tag_key}"\nlast open tag is "{last_sec

Error message

Trying to close tag "{tag_key}"\nlast open tag is "{last_section}"\nline {_CURRENT_LINE + 1}

What it means

Raised by the Mustache tokenizer in `langchain_core.utils.mustache` when a closing section tag `{{/name}}` does not match the most recently opened section. Mustache requires properly nested sections (LIFO), so `{{#a}}...{{/b}}` is rejected; the message names both the tag being closed and the actually-open section. Line numbers are one-based.

Source

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

            # Then check to see if the last opened section
            # is the same as us
            try:
                last_section = open_sections.pop()
            except IndexError as e:
                msg = (
                    f'Trying to close tag "{tag_key}"\n'
                    "Looks like it was not opened.\n"
                    f"line {_CURRENT_LINE + 1}"
                )
                raise ChevronError(msg) from e
            if tag_key != last_section:
                # Otherwise we need to complain
                msg = (
                    f'Trying to close tag "{tag_key}"\n'
                    f'last open tag is "{last_section}"\n'
                    f"line {_CURRENT_LINE + 1}"
                )
                raise ChevronError(msg)

        # Do the second check to see if we're a standalone
        is_standalone = r_sa_check(template, tag_type, is_standalone)

        # Which if we are
        if is_standalone:
            # Remove the stuff before the newline
            template = template.split("\n", 1)[-1]

            # Partials need to keep the spaces on their left
            if tag_type != "partial":
                # But other tags don't
                literal = literal.rstrip(" ")

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

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Reorder the closing tags so they mirror the opening order exactly (innermost closes first).
  2. Verify exact name matches between `{{#name}}`/`{{^name}}` openers and `{{/name}}` closers — the message tells you the expected name.
  3. Auto-format/lint Mustache templates in CI to enforce balanced nesting.

Example fix

# before
template = "{{#a}}A{{#b}}B{{/a}}{{/b}}"  # closes 'a' while 'b' is open

# after
template = "{{#a}}A{{#b}}B{{/b}}{{/a}}"
Defensive patterns

Strategy: validation

Validate before calling

import re

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

if not sections_balanced(template):
    raise ValueError("unbalanced mustache sections")

Try / catch

try:
    list(tokenize(template))
except ChevronError as e:
    # message names the tag and the actually-open section
    raise ValueError(f"invalid template: {e}") from e

Prevention

When it happens

Trigger: Interleaved/nested sections closed out of order, e.g. `{{#a}}{{#b}}...{{/a}}{{/b}}`, rendered via `MustachePlaceholderFormatter` or `tokenize`; or opener/closer names that differ by a typo or whitespace.

Common situations: Hand-nested loops/conditionals in prompt templates closed in the wrong order; renaming one of two similar sections (`{{#tool}}` vs `{{#tools}}`) and updating only one side; merging template fragments that break nesting.

Related errors


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