instructure/canvas-lms · error · StandardError

Invalid scope value. Valid options are: #

Error message

Invalid scope value. Valid options are: #{VALID_SCOPES.join(", ")}.

What it means

TableHeaderScopeRule#fix! maps a user-supplied value through scope_lookup_table to set the scope attribute (row/col/rowgroup/colgroup) on a table header. If the value is not in VALID_SCOPES, it raises StandardError listing the valid options, because an invalid scope attribute is invalid HTML and breaks screen-reader table navigation.

Solutions

  1. Pass a value exactly matching one of VALID_SCOPES (row, col, rowgroup, colgroup)
  2. Map the UI label back to a canonical scope value before calling fix!
  3. Keep client-side option list in sync with the rule's VALID_SCOPES (derive from shared constant/i18n keys)
  4. Rescue the error and re-render the form with the list of valid options

Example fix

// before
rule.fix!(th, 'column')
// after
rule.fix!(th, 'col')
Defensive patterns

Strategy: validation

Validate before calling

const VALID_SCOPES = ['row', 'col', 'rowgroup', 'colgroup'];
if (!VALID_SCOPES.includes(value)) {
  alert('Invalid scope value'); return;
}
rule.fix!(th, value)

Type guard

const isValidScope = (v) => ['row','col','rowgroup','colgroup'].includes(v)

Try / catch

begin
  rule.fix!(elem, value)
rescue StandardError => e
  render json: { error: e.message, valid: ['row','col','rowgroup','colgroup'] }, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: Calling fix! with a value not present in VALID_SCOPES (and not resolvable via scope_lookup_table), e.g. 'both', 'column', typo'd 'colum'; stale client sending an old enum value after the rule's options changed.

Common situations: Frontend option labels drift from backend expected values after localization changes; API consumers hard-coding scope strings; version mismatch between UI bundle and Rails code.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at app/models/accessibility/rules/table_header_scope_rule.rb:63

        Accessibility::Forms::RadioInputGroupField.new(
          label: I18n.t("Which part of the table does this heading apply to?"),
          undo_text: I18n.t("Heading scope is now set up."),
          value: options.first,
          options:,
          action: I18n.t("Set heading scope")
        )
      end

      def fix!(elem, value)
        scope_lookup_table = SCOPE_OPTIONS.to_h { |opt| [opt[:label].call, opt[:value]] }
        scope = scope_lookup_table[value]
        if scope
          return { changed: nil } if elem["scope"] == scope

          elem["scope"] = scope
        else
          raise StandardError, "Invalid scope value. Valid options are: #{VALID_SCOPES.join(", ")}." unless VALID_SCOPES.include?(value)
        end

        { changed: elem, content_preview: table_preview(elem) }
      end

      def display_name
        I18n.t("Table header set up incorrectly")
      end

      def message
        I18n.t("This table headers isn't set up correctly for screen readers to know which cells it applies to.")
      end

      def why
        I18n.t(
          "This table header doesn't have scope set up. " \
          "Scope tells screen readers which part of the table a heading applies to."
        )

View on GitHub (pinned to 1c9f0bb801)