Shopify/liquid · error · Liquid::SyntaxError
errors.syntax.cycle
errors.syntax.cycle
Error message
options[:locale].t("errors.syntax.cycle") What it means
The strict2_parse path for the `cycle` tag requires at least one expression: `cycle [name:] expression(, expression)*`. If the parser immediately sees end-of-string (empty markup), it raises SyntaxError with the localized 'errors.syntax.cycle'. This fires in strict error mode when a cycle tag has no arguments.
Solutions
- Provide at least one expression: {% cycle "odd", "even" %}
- Remove trailing commas after the last cycle value
- Verify interpolated markup is non-empty before rendering the tag
- Test templates under strict error_mode to catch these before production
Example fix
// before
{% cycle %}
// after
{% cycle "one", "two", "three" %} Defensive patterns
Strategy: validation
Validate before calling
def valid_cycle_markup?(markup)
!markup.strip.empty? && !markup.strip.end_with?(',')
end Try / catch
begin
Liquid::Template.parse(source)
rescue Liquid::SyntaxError => e
raise "cycle tag needs at least one value: #{e.message}"
end Prevention
- Provide at least one expression in every cycle tag
- Avoid trailing commas in cycle argument lists
- Validate interpolated markup is non-empty before render
When it happens
Trigger: `{% cycle %}` with empty markup under strict parsing; also a trailing comma like `{% cycle "a", %}` reaching end_of_string where an expression was expected.
Common situations: Templates generated programmatically with variables that interpolate to empty cycle arguments; strict error mode enabled in newer Liquid versions exposing previously tolerated lax markup; typos deleting the cycle values.
Related errors
- errors.syntax.unexpected_outer_tag
- errors.syntax.unknown_tag (locale: 'Unknown tag 'tag'')
- errors.syntax.assign
- errors.syntax.capture
- errors.syntax.case
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/531323bea0be29c5.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/tags/cycle.rb:64
output << val
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
output
end
private
# cycle [name:] expression(, expression)*
def strict2_parse(markup)
p = @parse_context.new_parser(markup)
@variables = []
raise SyntaxError, options[:locale].t("errors.syntax.cycle") if p.look(:end_of_string)
first_expression = safe_parse_expression(p)
if p.look(:colon)
# cycle name: expr1, expr2, ...
@name = first_expression
@is_named = true
p.consume(:colon)
# After the colon, parse the first variable (required for named cycles)
@variables << maybe_dup_lookup(safe_parse_expression(p))
else
# cycle expr1, expr2, ...
@variables << maybe_dup_lookup(first_expression)
end
# Parse remaining comma-separated expressions
while p.consume?(:comma)
break if p.look(:end_of_string)
View on GitHub (pinned to 807d45a6b3)