nodejs/node · error · TemplateSyntaxError

unexpected '%s', expected '%s'

Error message

unexpected '%s', expected '%s'

What it means

Sibling of 612: a closing bracket }, ), or ] was found and the stack was non-empty, but the expected opener (top of balancing_stack) did not match — e.g. opened with '[' but closed with ')'. The lexer reports both the actual and the expected bracket.

Source

Thrown at tools/inspector_protocol/jinja2/lexer.py:686

                # strings as token just are yielded as it.
                else:
                    data = m.group()
                    # update brace/parentheses balance
                    if tokens == 'operator':
                        if data == '{':
                            balancing_stack.append('}')
                        elif data == '(':
                            balancing_stack.append(')')
                        elif data == '[':
                            balancing_stack.append(']')
                        elif data in ('}', ')', ']'):
                            if not balancing_stack:
                                raise TemplateSyntaxError('unexpected \'%s\'' %
                                                          data, lineno, name,
                                                          filename)
                            expected_op = balancing_stack.pop()
                            if expected_op != data:
                                raise TemplateSyntaxError('unexpected \'%s\', '
                                                          'expected \'%s\'' %
                                                          (data, expected_op),
                                                          lineno, name,
                                                          filename)
                    # yield items
                    if data or tokens not in ignore_if_empty:
                        yield lineno, tokens, data
                    lineno += data.count('\n')

                # fetch new position into new variable so that we can check
                # if there is a internal parsing error which would result
                # in an infinite loop
                pos2 = m.end()

                # handle state changes
                if new_state is not None:
                    # remove the uppermost state
                    if new_state == '#pop':

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Read both symbols in the message: it tells you which closer was found and which was expected.
  2. Replace the wrong closer with the matching one for the innermost opener.

Example fix

{# before #}
{{ items[0) }}

{# after #}
{{ items[0] }}
Defensive patterns

Strategy: validation

Validate before calling

def brackets_match(expr: str) -> bool:
    pairs = {')': '(', ']': '[', '}': '{'}
    stack = []
    for ch in expr:
        if ch in '([{':
            stack.append(ch)
        elif ch in pairs:
            if not stack:
                return False
            if stack.pop() != pairs[ch]:
                return False
    return not stack

Try / catch

from jinja2.exceptions import TemplateSyntaxError
try:
    env.parse(source)
except TemplateSyntaxError as e:
    log.error('mismatched bracket at %s:%s: %s', name, e.lineno, e.message)

Prevention

When it happens

Trigger: Mismatched bracket pairs such as {{ foo[bar) }}, {{ func(x] }}, or {{ dict('k': v) }} confusing ( with [.

Common situations: Mixing parentheses, brackets, and braces when nesting calls/subscripts/literals; refactor typos.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/59a7ac5a6b193323. Report an issue: GitHub.