{"record":{"id":"ef5e259f67f99efb","repo":"instructure/canvas-lms","slug":"count-must-be-0","errorCode":null,"errorMessage":"COUNT must be > 0","messagePattern":"COUNT must be > 0","errorType":"validation","errorClass":"RruleValidationError","httpStatus":null,"severity":"error","filePath":"app/helpers/rrule_helper.rb","lineNumber":64,"sourceCode":"  rescue => e\n    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\"),","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/rrule_helper.rb#L46-L82","documentation":"When the RRULE includes a COUNT clause, rrule_validate_common_opts requires it to be a positive integer. The check `rropts[\"COUNT\"].to_i > 0` fails when COUNT is 0, negative, or a non-numeric string (which .to_i turns into 0), so RruleValidationError is raised.","triggerScenarios":"Calling RruleHelper.rrule_to_natural_language with rropts containing \"COUNT\" => \"0\", \"COUNT\" => \"-5\", \"COUNT\" => \"abc\" (non-numeric), or an empty string — any value whose to_i is not > 0.","commonSituations":"Client sends COUNT=0 thinking it means 'unlimited'; a UI defaults the count field to 0 when the user never filled it in; string parsing of the RRULE produces an empty or malformed COUNT value; off-by-one bugs generating counts from an empty occurrence list.","solutions":["Ensure COUNT is a positive integer string (e.g. COUNT=5) in the RRULE","If COUNT is optional in your flow, use UNTIL instead of emitting an invalid COUNT","Validate the recurrence input at the API boundary before calling RruleHelper"],"exampleFix":"// before\n\"FREQ=DAILY;INTERVAL=1;COUNT=0\"\n// after\n\"FREQ=DAILY;INTERVAL=1;COUNT=10\"","handlingStrategy":"validation","validationCode":"count = rropts[\"COUNT\"].to_i\nraise ArgumentError, \"COUNT must be a positive integer\" unless rropts.key?(\"COUNT\") && count > 0","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":["Never default COUNT to 0 in UI forms","Coerce/validate COUNT as a positive integer before building the RRULE","Check for empty-string COUNT values from form inputs"],"tags":["rrule","validation","count","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"}