{"record":{"id":"5982cbe19e545bb4","repo":"instructure/canvas-lms","slug":"missing-interval","errorCode":null,"errorMessage":"Missing INTERVAL","messagePattern":"Missing INTERVAL","errorType":"validation","errorClass":"RruleValidationError","httpStatus":null,"severity":"warning","filePath":"app/helpers/rrule_helper.rb","lineNumber":56,"sourceCode":"      parse_weekly(rropts)\n    when \"MONTHLY\"\n      parse_monthly(rropts)\n    when \"YEARLY\"\n      parse_yearly(rropts)\n    else\n      raise RruleValidationError, I18n.t(\"Invalid FREQ '%{freq}'\", freq: rropts[\"FREQ\"])\n    end\n  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","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/rrule_helper.rb#L38-L74","documentation":"rrule_validate_common_opts requires an INTERVAL key in the parsed RRULE options before computing a natural-language description; without it, RruleValidationError 'Missing INTERVAL' is raised (then logged and swallowed to nil by rrule_to_natural_language's rescue).","triggerScenarios":"Rendering calendar_event_json for an event whose rrule string omits INTERVAL, e.g. 'RRULE:FREQ=DAILY;COUNT=5' (RFC 5545 makes INTERVAL default 1, so producers often omit it).","commonSituations":"ICS imports from producers that legally omit INTERVAL; hand-authored rrule strings in seeds/tests; legacy rows written before validation existed.","solutions":["Default INTERVAL to 1 when absent before calling the helper: rropts['INTERVAL'] ||= 1.","Fix the data: rewrite stored rrule strings to include INTERVAL.","Normalize at import time so all stored RRULEs carry explicit INTERVAL.","Rescue RruleValidationError in the JSON rendering and omit the natural-language field gracefully."],"exampleFix":"// before\nrropts = RruleHelper.rrule_parse(event.rrule)\ndesc = helper.rrule_to_natural_language(event.rrule)\n// after\nrropts = RruleHelper.rrule_parse(event.rrule)\nrropts['INTERVAL'] ||= '1'\ndesc = helper.rrule_to_natural_language(event.rrule)","handlingStrategy":"validation","validationCode":"rropts['INTERVAL'] ||= '1'\nraise ArgumentError, 'missing INTERVAL' unless rropts.key?('INTERVAL')","typeGuard":null,"tryCatchPattern":"begin\n  desc = rrule_to_natural_language(rrule)\nrescue RruleValidationError\n  desc = nil\nend","preventionTips":["RFC 5545 defaults INTERVAL to 1 — inject it when absent","Normalize all imported RRULEs to carry explicit INTERVAL","Add a model-level validation for RRULE option completeness"],"tags":["rrule","calendar","missing-option"],"backgroundTag":"missing-required-argument","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"}