Shopify/liquid · error · Liquid::SyntaxError
Unexpected character #
Error message
Unexpected character #{ss.getch} What it means
When the lexer's regex scanner cannot match any known token, raise_syntax_error rewinds to the start position and raises Liquid::SyntaxError reporting the unexpected character (using getch so multi-byte UTF-8 characters are reported whole). It means the template contains a character that cannot begin any valid Liquid token.
Solutions
- Locate the reported character in the template and fix or remove it (check for lone '{' or non-ASCII punctuation).
- Fix the delimiter to a valid one: {% ... %}, {{ ... }}, or a raw '{' that does not look like a broken tag start.
- Re-save the file as clean UTF-8 without smart quotes/control characters.
- Add a parse smoke test (Template.parse) in CI for templates edited by hand.
Example fix
// before
{ if x %}hello{{
// after
{% if x %}hello{% endif %} Defensive patterns
Strategy: validation
Validate before calling
def no_lone_braces?(src)
!src.match?(/\{(?![{%])/) # no '{' not followed by '{' or '%'
end Try / catch
begin
Liquid::Template.parse(src)
rescue Liquid::SyntaxError => e
if (ch = e.message[/Unexpected character (.+)/, 1])
report_syntax_location(src, ch)
end
raise
end Prevention
- Escape literal braces in template text where needed.
- Ban smart quotes/control characters in template files via editorconfig/lint.
- Run Template.parse on every template in CI to catch tokenization breakage early.
- Fix delimiters to exactly {% %} or {{ }} — beware typos like {- ... -}.
When it happens
Trigger: Template.parse on source containing a stray character that breaks tokenization — e.g. an unmatched '{' not followed by '%' or '{', invalid control characters, or garbage bytes near tag delimiters.
Common situations: Hand-edited templates with a lone '{' or '}' mishandled; copy-paste introducing smart quotes or invisible control characters; templating scripts emitting malformed delimiters (e.g. '{ x }' or '{- if -}').
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid byte sequence in #
- Nesting too deep
- errors.syntax.unexpected_outer_tag
- errors.syntax.unknown_tag (locale: 'Unknown tag 'tag'')
- undefined method #
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/717d2f5dd49a57e1.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/lexer.rb:175
else
raise_syntax_error(start_pos, ss)
end
end
end
# rubocop:enable Metrics/BlockNesting
output << EOS
rescue ::ArgumentError => e
if e.message == "invalid byte sequence in #{ss.string.encoding}"
raise SyntaxError, "Invalid byte sequence in #{ss.string.encoding}"
else
raise
end
end
def raise_syntax_error(start_pos, ss)
ss.pos = start_pos
# the character could be a UTF-8 character, use getch to get all the bytes
raise SyntaxError, "Unexpected character #{ss.getch}"
end
end
end
end
View on GitHub (pinned to 807d45a6b3)