Shopify/liquid · error · Liquid::ArgumentError

e.message (re-raised as Liquid::ArgumentError from…

Error message

e.message (re-raised as Liquid::ArgumentError from ::ArgumentError during operator invocation)

What it means

When a comparison operator is applied via `left.send(operation, right)` and Ruby raises ::ArgumentError, Liquid re-raises it as Liquid::ArgumentError carrying the original message. This happens when an object's operator method (e.g. <=>, ==, a custom method) rejects the right operand's type, or the object misuses its own API internally.

Solutions

  1. Fix the comparison in the template so both sides share a type (e.g. compare numbers to numbers).
  2. Coerce values before comparison via a filter or by assigning normalized variables.
  3. If you own a custom drop/object, correct its operator implementation or arity.
  4. Wrap render in error handling to log e.message and identify the offending method.

Example fix

// before
{% if price > "100" %}...{% endif %}
// after
{% assign limit = 100 %}
{% if price > limit %}...{% endif %}
Defensive patterns

Strategy: type-guard

Validate before calling

def comparable?(a, b)
  (a.is_a?(Numeric) && b.is_a?(Numeric)) || (a.is_a?(String) && b.is_a?(String))
end
# guard template conditions: only compare same-typed values

Type guard

def safe_compare(left, right, op)
  return false unless left.respond_to?(op) && right.respond_to?(op)
  return false unless left.class == right.class || (left.is_a?(Numeric) && right.is_a?(Numeric))
  true
end

Try / catch

begin
  html = template.render(assigns)
rescue Liquid::ArgumentError => e
  logger.warn("liquid comparison failed: #{e.message}")
  html = template.render(normalized_assigns)
end

Prevention

When it happens

Trigger: Comparing incompatible types in `{% if x > y %}` (e.g. String vs Integer with a String#> that raises); custom drop/objects defining operators that raise ArgumentError; using an operator name that resolves to a method with wrong arity; non-Hash operands where left/right both respond_to? the operator.

Common situations: Comparing nil-wrapped values or strings that look numeric; custom Liquid drops with badly implemented <=> or respond_to?; template conditions on locale-formatted numbers; upgrading Ruby changing operator semantics.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08). Data as JSON: /api/errors/7a7a45e7658e35b7. Report an issue: GitHub.

Appendix: source

Thrown at lib/liquid/condition.rb:194

    def interpret_condition(left, right, op, context)
      # If the operator is empty this means that the decision statement is just
      # a single variable. We can just poll this variable from the context and
      # return this as the result.
      return context.evaluate(left) if op.nil?

      left  = Liquid::Utils.to_liquid_value(context.evaluate(left))
      right = Liquid::Utils.to_liquid_value(context.evaluate(right))

      operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")

      if operation.respond_to?(:call)
        operation.call(self, left, right)
      elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
        begin
          left.send(operation, right)
        rescue ::ArgumentError => e
          raise Liquid::ArgumentError, e.message
        end
      end
    end

    def deprecated_default_context
      warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated " \
        "and will be removed from Liquid 6.0.0.")
      Context.new
    end

    class ParseTreeVisitor < Liquid::ParseTreeVisitor
      def children
        [
          @node.left,
          @node.right,
          @node.child_condition,
          @node.attachment
        ].compact

View on GitHub (pinned to 807d45a6b3)