instructure/canvas-lms · error · RruleValidationError
COUNT must be > 0
Error message
COUNT must be > 0
What it means
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.
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
Example fix
// before "FREQ=DAILY;INTERVAL=1;COUNT=0" // after "FREQ=DAILY;INTERVAL=1;COUNT=10"
Defensive patterns
Strategy: validation
Validate before calling
count = rropts["COUNT"].to_i
raise ArgumentError, "COUNT must be a positive integer" unless rropts.key?("COUNT") && count > 0 Try / catch
begin
RruleHelper.rrule_to_natural_language(rropts)
rescue RruleValidationError => e
render json: { errors: [e.message] }, status: :bad_request
end Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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/ef5e259f67f99efb.
Report an issue: GitHub.
Appendix: source
Thrown at app/helpers/rrule_helper.rb:64
rescue => e
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"),View on GitHub (pinned to 1c9f0bb801)