Why am I getting production errors in my script editor?

Topic summary

Issue: Users are encountering production errors when running scripts in Shopify’s Script Editor, despite scripts working fine in testing. The primary error is “InstructionQuotaExceeded,” triggered when customers create extremely large carts (e.g., 1 million quantity items).

Root Cause: The scripts fail when processing carts with abnormally high quantities or item counts, likely from users attempting to exploit discounts or break checkout.

Workarounds Shared:

  • rancet’s solution: Added boundary conditions to detect oversized carts before applying discounts. If quantities exceed safe thresholds, the script ignores the cart entirely rather than attempting discount calculations.
  • Kneeko’s approach: Implemented a CartCleanupCampaign class that removes line items exceeding a maximum quantity (200 per item) before processing, preventing the quota error.

Current Status: No official resolution exists. Multiple users report the same issue. The shared solutions are temporary fixes, as Shopify Scripts will be deprecated soon. Users are implementing cart validation checks (max items: 20, max quantity: 80-200) to prevent script execution failures.

Summarized with AI on November 12. AI used: claude-sonnet-4-5-20250929.

I’ve been seeing production errors my every script I run in the Script Editor. Everything tested fine but I’m unable to resolve this. Any help would be much obliged.

Below is the script I am running.

class Campaign
  def initialize(condition, *qualifiers)
    @condition = (condition.to_s + '?').to_sym
    @qualifiers = PostCartAmountQualifier ? [] : [] rescue qualifiers.compact
    @line_item_selector = qualifiers.last unless @line_item_selector
    qualifiers.compact.each do |qualifier|
      is_multi_select = qualifier.instance_variable_get(:@conditions).is_a?(Array)
      if is_multi_select
        qualifier.instance_variable_get(:@conditions).each do |nested_q|
          @post_amount_qualifier = nested_q if nested_q.is_a?(PostCartAmountQualifier)
          @qualifiers << qualifier
        end
      else
        @post_amount_qualifier = qualifier if qualifier.is_a?(PostCartAmountQualifier)
        @qualifiers << qualifier
      end
    end if @qualifiers.empty?
  end

  def qualifies?(cart)
    return true if @qualifiers.empty?
    @unmodified_line_items = cart.line_items.map do |item|
      new_item = item.dup
      new_item.instance_variables.each do |var|
        val = item.instance_variable_get(var)
        new_item.instance_variable_set(var, val.dup) if val.respond_to?(:dup)
      end
      new_item
    end if @post_amount_qualifier
    @qualifiers.send(@condition) do |qualifier|
      is_selector = false
      if qualifier.is_a?(Selector) || qualifier.instance_variable_get(:@conditions).any? { |q| q.is_a?(Selector) }
        is_selector = true
      end rescue nil
      if is_selector
        raise "Missing line item match type" if @li_match_type.nil?
        cart.line_items.send(@li_match_type) { |item| qualifier.match?(item) }
      else
        qualifier.match?(cart, @line_item_selector)
      end
    end
  end

  def run_with_hooks(cart)
    before_run(cart) if respond_to?(:before_run)
    run(cart)
    after_run(cart)
  end

  def after_run(cart)
    @discount.apply_final_discount if @discount && @discount.respond_to?(:apply_final_discount)
    revert_changes(cart) unless @post_amount_qualifier.nil? || @post_amount_qualifier.match?(cart)
  end

  def revert_changes(cart)
    cart.instance_variable_set(:@line_items, @unmodified_line_items)
  end
end

class BuyXGetX < Campaign
  def initialize(condition, customer_qualifier, cart_qualifier, buy_item_selector, get_item_selector, discount, buy_x, get_x, max_sets)
    super(condition, customer_qualifier, cart_qualifier)
    @line_item_selector = buy_item_selector
    @get_item_selector = get_item_selector
    @discount = discount
    @buy_x = buy_x
    @get_x = get_x
    @max_sets = max_sets == 0 ? nil : max_sets
  end

  def run(cart)
    raise "Campaign requires a discount" unless @discount
    return unless qualifies?(cart)
    return unless cart.line_items.reduce(0) {|total, item| total += item.quantity } >= @buy_x
    applicable_buy_items = nil
    eligible_get_items = nil
    discountable_sets = 0

    # Find the items that qualify for buy_x
    if @line_item_selector.nil?
      applicable_buy_items = cart.line_items
    else
      applicable_buy_items = cart.line_items.select { |item| @line_item_selector.match?(item) }
    end

    # Find the items that qualify for get_x
    if @get_item_selector.nil?
      eligible_get_items = cart.line_items
    else
      eligible_get_items = cart.line_items.select {|item| @get_item_selector.match?(item) }
    end

    # Check if cart qualifies for discounts and limit the discount sets
    purchased_quantity = applicable_buy_items.reduce(0) { |total, item| total += item.quantity }
    discountable_sets = (@max_sets ? [purchased_quantity / @buy_x, @max_sets].min : purchased_quantity / @buy_x).to_i
    return if discountable_sets < 1
    discountable_quantity = (discountable_sets * @get_x).to_i
    # Apply the discounts (sort to discount lower priced items first)
    eligible_get_items = eligible_get_items.sort_by { |item| item.variant.price }
    eligible_get_items.each do |item|
      break if discountable_quantity == 0
      if item.quantity <= discountable_quantity
        @discount.apply(item)
        discountable_quantity -= item.quantity
      else
        new_item = item.split({ take: discountable_quantity })
        @discount.apply(new_item)
        cart.line_items << new_item
        discountable_quantity = 0
      end
    end
  end
end
class ConditionalDiscount < Campaign
  def initialize(condition, customer_qualifier, cart_qualifier, line_item_selector, discount, max_discounts)
    super(condition, customer_qualifier, cart_qualifier)
    @line_item_selector = line_item_selector
    @discount = discount
    @items_to_discount = max_discounts == 0 ? nil : max_discounts
  end

  def run(cart)
    raise "Campaign requires a discount" unless @discount
    return unless qualifies?(cart)
    applicable_items = cart.line_items.select { |item| @line_item_selector.nil? || @line_item_selector.match?(item) }
    applicable_items = applicable_items.sort_by { |item| item.variant.price }
    applicable_items.each do |item|
      break if @items_to_discount == 0
      if (!@items_to_discount.nil? && item.quantity > @items_to_discount)
        discounted_items = item.split(take: @items_to_discount)
        @discount.apply(discounted_items)
        cart.line_items << discounted_items
        @items_to_discount = 0
      else
        @discount.apply(item)
        @items_to_discount -= item.quantity if !@items_to_discount.nil?
      end
    end
  end
end
class Qualifier
  def partial_match(match_type, item_info, possible_matches)
    match_type = (match_type.to_s + '?').to_sym
    if item_info.kind_of?(Array)
      possible_matches.any? do |possibility|
        item_info.any? do |search|
          search.send(match_type, possibility)
        end
      end
    else
      possible_matches.any? do |possibility|
        item_info.send(match_type, possibility)
      end
    end
  end

  def compare_amounts(compare, comparison_type, compare_to)
    case comparison_type
      when :greater_than
        return compare > compare_to
      when :greater_than_or_equal
        return compare >= compare_to
      when :less_than
        return compare < compare_to
      when :less_than_or_equal
        return compare <= compare_to
      when :equal_to
        return compare == compare_to
      else
        raise "Invalid comparison type"
    end
  end
end

class CartHasItemQualifier < Qualifier
  def initialize(quantity_or_subtotal, comparison_type, amount, item_selector)
    @quantity_or_subtotal = quantity_or_subtotal
    @comparison_type = comparison_type
    @amount = quantity_or_subtotal == :subtotal ? Money.new(cents: amount * 100) : amount
    @item_selector = item_selector
  end

  def match?(cart, selector = nil)
    raise "Must supply an item selector for the #{self.class}" if @item_selector.nil?
    case @quantity_or_subtotal
      when :quantity
        total = cart.line_items.reduce(0) do |total, item|
          total + (@item_selector&.match?(item) ? item.quantity : 0)
        end
      when :subtotal
        total = cart.line_items.reduce(Money.zero) do |total, item|
          total + (@item_selector&.match?(item) ? item.line_price : Money.zero)
        end
    end
    compare_amounts(total, @comparison_type, @amount)
  end
end

class CustomerTagQualifier < Qualifier
  def initialize(match_type, match_condition, tags)
    @match_condition = match_condition
    @invert = match_type == :does_not
    @tags = tags.map(&:downcase)
  end

  def match?(cart, selector = nil)
    return true if cart.customer.nil? && @invert
    return false if cart.customer.nil?
    customer_tags = cart.customer.tags.to_a.map(&:downcase)
    case @match_condition
      when :match
        return @invert ^ ((@tags & customer_tags).length > 0)
      else
        return @invert ^ partial_match(@match_condition, customer_tags, @tags)
    end
  end
end

class Selector
  def partial_match(match_type, item_info, possible_matches)
    match_type = (match_type.to_s + '?').to_sym
    if item_info.kind_of?(Array)
      possible_matches.any? do |possibility|
        item_info.any? do |search|
          search.send(match_type, possibility)
        end
      end
    else
      possible_matches.any? do |possibility|
        item_info.send(match_type, possibility)
      end
    end
  end
end

class ProductIdSelector < Selector
  def initialize(match_type, product_ids)
    @invert = match_type == :not_one
    @product_ids = product_ids.map { |id| id.to_i }
  end

  def match?(line_item)
    @invert ^ @product_ids.include?(line_item.variant.product.id)
  end
end

class ProductVendorSelector < Selector
  def initialize(match_type, vendors)
    @invert = match_type != :is_one
    @vendors = vendors.map(&:downcase)
  end

  def match?(line_item)
    @invert ^ @vendors.include?(line_item.variant.product.vendor.downcase)
  end
end

class PercentageDiscount
  def initialize(percent, message)
    @discount = (100 - percent) / 100.0
    @message = message
  end

  def apply(line_item)
    line_item.change_line_price(line_item.line_price * @discount, message: @message)
  end
end

CAMPAIGNS = [
  BuyXGetX.new(
    :all,
    nil,
    CartHasItemQualifier.new(
      :quantity,
      :greater_than_or_equal,
      1,
      ProductIdSelector.new(
        :is_one,
        ["2423846273082", "2423850860602", "2424427282490", "4379124105274", "4379125776442"]
      )
    ),
    ProductIdSelector.new(
      :is_one,
      ["2423846273082", "2423850860602", "2424427282490", "4379124105274", "4379125776442"]
    ),
    ProductIdSelector.new(
      :is_one,
      ["610077245498"]
    ),
    PercentageDiscount.new(
      100,
      ""
    ),
    1,
    1,
    0
  ),
  ConditionalDiscount.new(
    :all,
    CustomerTagQualifier.new(
      :does,
      :start_with,
      ["pro"]
    ),
    CartHasItemQualifier.new(
      :quantity,
      :greater_than,
      0,
    ProductVendorSelector.new(
        :is_one,
        ["Aprés Nail", "Apres Nail"]
      )
    ),
    ProductVendorSelector.new(
      :is_one,
      ["Apres Nail", "Aprés Nail"]
    ),
    PercentageDiscount.new(
      20,
      "Pro Member Discount!"
    ),
    0
  )
].freeze

CAMPAIGNS.each do |campaign|
  campaign.run_with_hooks(Input.cart)
end

Output.cart = Input.cart

Hi, did you resolve this? I have recently posted a message about “InstructionQuotaExceeded” where customers are created very large carts (entering 1million in the qty etc) & I am interested in ways to detect & prevent such errors. I wondered if you were seeing a similar problem if you’re code worked in testing?

Seeing a similar issue myslef. Did you ever get resoltion to this?

unfortunately no solution yet.

I am also seeing that issue on our site.

Hi everyone,

I worked around this problem by placing some boundary conditions in my code after experimenting with cart sizes & working out where the limits get exceeded.

I assume these attempts are trying to get free stuff or trying to break the checkout, so if the numbers are too high my code simply ignores the cart & doesn’t even attempt to apply a discount…

Here’s my main class that handles this before applying the discount code (which I have left out for simplicity).

class PromotionCampaign
  def initialize(cart)
     = cart
    _items = 20
    _qty = 80
  end
   
  def run!
    if(@cart.line_items.count <= _items)
      if(@cart.line_items.map {|cart_line| cart_line.quantity }.inject(0, :+)<=@max_qty)
        discounted_products = ProductBuyXForX.new(@cart).run!
      end
    end
    
  end
   
end

Hope that is useful to someone!

2 Likes

Modified your code to accomplish a similar solution, but the result is that if the product max quantity is exceeded, the line item is just removed. We were seeing the all too familiar InstructionQuotaExceeded issue with carts having qty’s of 1 million per line item - so this is what we’ve done. It’s a short lived solution since scripts will be going away soon enough; however, some peace in the interim:

# Prevent Insane Cart Quantities
class CartCleanupCampaign
  def initialize(cart)
     = cart
    _qty = 200 # Maximum allowed quantity per line item
  end

  def run!
    .line_items.each do |line_item|
      # Check if the line item's quantity exceeds the maximum allowed quantity
      if line_item.quantity > _qty
        # Remove the line item from the cart
        .line_items.delete(line_item)
      end
    end
    
  end
end

CartCleanupCampaign.new(Input.cart).run!

check_cart(Input.cart)

Output.cart = Input.cart
1 Like