instructure/canvas-lms · error · RruleValidationError

COUNT must be <=

Error message

COUNT must be <= %{limit}

What it means

Canvas materializes every occurrence of a recurring series as database rows, so series length is capped at RruleHelper::RECURRING_EVENT_LIMIT. If rropts["COUNT"].to_i exceeds that limit, rrule_validate_common_opts raises RruleValidationError with the limit interpolated into the message.

Solutions

  1. Reduce COUNT to at most RruleHelper::RECURRING_EVENT_LIMIT
  2. Split the series into multiple smaller recurring events (chunks within the limit)
  3. Check the limit constant and reject/clip the value in the API layer before invoking RruleHelper

Example fix

// before
"FREQ=DAILY;INTERVAL=1;COUNT=9999"
// after
"FREQ=DAILY;INTERVAL=1;COUNT=365" # <= RruleHelper::RECURRING_EVENT_LIMIT
Defensive patterns

Strategy: validation

Validate before calling

limit = RruleHelper::RECURRING_EVENT_LIMIT
count = rropts["COUNT"].to_i
raise ArgumentError, "COUNT exceeds #{limit}" if count > limit

Try / catch

begin
  RruleHelper.rrule_to_natural_language(rropts)
rescue RruleValidationError => e
  render json: { errors: [e.message] }, status: :bad_request
end

Prevention

When it happens

Trigger: Calling RruleHelper.rrule_to_natural_language with an rropts hash whose "COUNT" value is a positive integer greater than RruleHelper::RECURRING_EVENT_LIMIT — e.g. COUNT=1000 when the limit is lower.

Common situations: Importing ICS calendars with year-long or infinite daily recurrences encoded as very large COUNTs; clients computing COUNT as days-in-a-range without checking the platform cap; UIs letting users pick arbitrary end counts.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/91958fc02745e69c. Report an issue: GitHub.

Appendix: source

Thrown at app/helpers/rrule_helper.rb:65

    logger.error "RRULE to natural language failure: #{e}"
    nil
  end

  def rrule_parse(rrule)
    Hash[*rrule.sub(/^RRULE:/, "").split(/[;=]/)]
  end

  def rrule_validate_common_opts(rropts)
    raise RruleValidationError, I18n.t("Missing INTERVAL") unless rropts.key?("INTERVAL")
    raise RruleValidationError, I18n.t("INTERVAL must be > 0") unless rropts["INTERVAL"].to_i > 0

    # We do not support never ending series because each event in the series
    # must get created in the db to support the paginated calendar_events api
    raise RruleValidationError, I18n.t("Missing COUNT or UNTIL") unless rropts.key?("COUNT") || rropts.key?("UNTIL")

    if rropts.key?("COUNT")
      raise RruleValidationError, I18n.t("COUNT must be > 0") unless rropts["COUNT"].to_i > 0
      raise RruleValidationError, I18n.t("COUNT must be <= %{limit}", limit: RruleHelper::RECURRING_EVENT_LIMIT) unless rropts["COUNT"].to_i <= RruleHelper::RECURRING_EVENT_LIMIT
    else
      begin
        format_date(rropts["UNTIL"])
      rescue
        raise RruleValidationError, I18n.t("Invalid UNTIL '%{until_date}'", until_date: rropts["UNTIL"])
      end
    end
  end

  private

  DAYS_OF_WEEK = {
    "SU" => I18n.t("Sun"),
    "MO" => I18n.t("Mon"),
    "TU" => I18n.t("Tue"),
    "WE" => I18n.t("Wed"),
    "TH" => I18n.t("Thu"),
    "FR" => I18n.t("Fri"),

View on GitHub (pinned to 1c9f0bb801)