nodejs/node · error · TemplateSyntaxError
unexpected char %r at %d
Error message
unexpected char %r at %d
What it means
The tokenizer loops over a set of regex rules to advance through the source. If no rule matches at the current position and the position is not at end-of-source, the character is untokenizable and Jinja2 raises TemplateSyntaxError('unexpected char %r at %d') with the offending character and byte offset.
Source
Thrown at tools/inspector_protocol/jinja2/lexer.py:737
stack.append(new_state)
statetokens = self.rules[stack[-1]]
# we are still at the same position and no stack change.
# this means a loop without break condition, avoid that and
# raise error
elif pos2 == pos:
raise RuntimeError('%r yielded empty string without '
'stack change' % regex)
# publish new function and start again
pos = pos2
break
# if loop terminated without break we haven't found a single match
# either we are at the end of the file or we have a problem
else:
# end of text
if pos >= source_length:
return
# something went wrong
raise TemplateSyntaxError('unexpected char %r at %d' %
(source[pos], pos), lineno,
name, filename)
View on GitHub (pinned to 1b2de5e052)
Solutions
- Inspect the character and offset reported in the message and remove or escape it.
- Strip non-ASCII/control characters from the template source (e.g. replace smart quotes and non-breaking spaces).
- If the character is intentional inside text, move it out of an expression/tag into literal template text.
Example fix
{# before #}
{{ user.@name }}
{# after #}
{{ user.name }} Defensive patterns
Strategy: validation
Validate before calling
import unicodedata
def normalize_template_source(source: str) -> str:
# replace smart quotes / non-breaking spaces that confuse the lexer
source = source.replace('\u201c', '"').replace('\u201d', '"')
source = source.replace('\u2018', "'").replace('\u2019', "'")
source = source.replace('\u00a0', ' ')
if any(unicodedata.category(ch) == 'Cc' and ch not in '\n\t' for ch in source):
raise ValueError('control characters found in template source')
return source Try / catch
from jinja2.exceptions import TemplateSyntaxError
try:
env.parse(source)
except TemplateSyntaxError as e:
if 'unexpected char' in str(e):
log.error('remove/escape the offending character at offset %s', e.lineno)
raise Prevention
- Paste templates as plain ASCII/UTF-8 text, not from rich-text editors.
- Strip control and zero-width characters before parsing.
- Keep foreign template-language syntax out of Jinja2 files.
When it happens
Trigger: A character that does not begin any valid token at that point — for example an unescaped special symbol inside an expression, a stray $ or @ (outside Jinja2's grammar), a control character, or invalid operator combinations.
Common situations: Pasting code containing syntax from another template language (Liquid, Twig, Mustache); invisible/control characters in the source; non-breaking spaces or smart quotes copied from a word processor.
Related errors
- Invalid character in identifier
- unexpected '%s'
- unexpected '%s', expected '%s'
- chunk after expression
- unexpected end of template, expected %r.
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/812c2cc384c0a669.
Report an issue: GitHub.