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

  1. Provide at least one expression: {% cycle "odd", "even" %}
  2. Remove trailing commas after the last cycle value
  3. Verify interpolated markup is non-empty before rendering the tag
  4. 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

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


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)