{"record":{"id":"91958fc02745e69c","repo":"instructure/canvas-lms","slug":"count-must-be-limit","errorCode":null,"errorMessage":"COUNT must be <= %{limit}","messagePattern":"COUNT must be <= %(.+?)","errorType":"validation","errorClass":"RruleValidationError","httpStatus":null,"severity":"error","filePath":"app/helpers/rrule_helper.rb","lineNumber":65,"sourceCode":"    logger.error \"RRULE to natural language failure: #{e}\"\n    nil\n  end\n\n  def rrule_parse(rrule)\n    Hash[*rrule.sub(/^RRULE:/, \"\").split(/[;=]/)]\n  end\n\n  def rrule_validate_common_opts(rropts)\n    raise RruleValidationError, I18n.t(\"Missing INTERVAL\") unless rropts.key?(\"INTERVAL\")\n    raise RruleValidationError, I18n.t(\"INTERVAL must be > 0\") unless rropts[\"INTERVAL\"].to_i > 0\n\n    # We do not support never ending series because each event in the series\n    # must get created in the db to support the paginated calendar_events api\n    raise RruleValidationError, I18n.t(\"Missing COUNT or UNTIL\") unless rropts.key?(\"COUNT\") || rropts.key?(\"UNTIL\")\n\n    if rropts.key?(\"COUNT\")\n      raise RruleValidationError, I18n.t(\"COUNT must be > 0\") unless rropts[\"COUNT\"].to_i > 0\n      raise RruleValidationError, I18n.t(\"COUNT must be <= %{limit}\", limit: RruleHelper::RECURRING_EVENT_LIMIT) unless rropts[\"COUNT\"].to_i <= RruleHelper::RECURRING_EVENT_LIMIT\n    else\n      begin\n        format_date(rropts[\"UNTIL\"])\n      rescue\n        raise RruleValidationError, I18n.t(\"Invalid UNTIL '%{until_date}'\", until_date: rropts[\"UNTIL\"])\n      end\n    end\n  end\n\n  private\n\n  DAYS_OF_WEEK = {\n    \"SU\" => I18n.t(\"Sun\"),\n    \"MO\" => I18n.t(\"Mon\"),\n    \"TU\" => I18n.t(\"Tue\"),\n    \"WE\" => I18n.t(\"Wed\"),\n    \"TH\" => I18n.t(\"Thu\"),\n    \"FR\" => I18n.t(\"Fri\"),","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/rrule_helper.rb#L47-L83","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Reduce COUNT to at most RruleHelper::RECURRING_EVENT_LIMIT","Split the series into multiple smaller recurring events (chunks within the limit)","Check the limit constant and reject/clip the value in the API layer before invoking RruleHelper"],"exampleFix":"// before\n\"FREQ=DAILY;INTERVAL=1;COUNT=9999\"\n// after\n\"FREQ=DAILY;INTERVAL=1;COUNT=365\" # <= RruleHelper::RECURRING_EVENT_LIMIT","handlingStrategy":"validation","validationCode":"limit = RruleHelper::RECURRING_EVENT_LIMIT\ncount = rropts[\"COUNT\"].to_i\nraise ArgumentError, \"COUNT exceeds #{limit}\" if count > limit","typeGuard":null,"tryCatchPattern":"begin\n  RruleHelper.rrule_to_natural_language(rropts)\nrescue RruleValidationError => e\n  render json: { errors: [e.message] }, status: :bad_request\nend","preventionTips":["Clip or reject COUNT at the client with the same limit constant","Import ICS rules by chunking long series","Read RruleHelper::RECURRING_EVENT_LIMIT rather than hardcoding a guess"],"tags":["rrule","validation","limit","calendar"],"backgroundTag":"value-out-of-range","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}