instructure/canvas-lms · error
# is invalid
Error message
#{content_item} is invalid What it means
submission_read_state validates the optional content_item against the CONTENT_ITEMS allowlist and raises "#{content_item} is invalid" for any value not in the list, since it is used directly as a query column.
Solutions
- Pass one of the exact values in ContentParticipation::CONTENT_ITEMS
- Normalize/whitelist user-supplied content_item params before the call
- Check CONTENT_ITEMS in this file to confirm the current allowed keys
Example fix
# before ContentParticipation.submission_item_read?(content:, user:, content_item: params[:item].to_sym) # after item = params[:item].to_sym ContentParticipation.submission_item_read?(content:, user:, content_item: item) if ContentParticipation::CONTENT_ITEMS.include?(item)
Defensive patterns
Strategy: validation
Validate before calling
item = params[:content_item]&.to_sym raise 'invalid content_item' unless item.nil? || ContentParticipation::CONTENT_ITEMS.include?(item)
Type guard
valid_item = ->(v) { v.nil? || ContentParticipation::CONTENT_ITEMS.include?(v) } Try / catch
begin
ContentParticipation.submission_item_read?(content:, user:, content_item: item)
rescue RuntimeError => e
raise e unless e.message.end_with?('is invalid')
# fall back to whole-content read state without content_item
end Prevention
- Whitelist content_item from user input against CONTENT_ITEMS
- Grep call sites whenever CONTENT_ITEMS enum changes
- Prefer the enum constant over hand-typed symbols
When it happens
Trigger: Calling submission_read_state/submission_item_read? with a content_item symbol/string not in ContentParticipation::CONTENT_ITEMS (typo, wrong enum value, atom key from an API payload).
Common situations: Clients pass arbitrary content_item values from request params; enum renamed in a refactor so old callers pass obsolete keys; symbol vs string mismatch not normalized before the allowlist check.
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
- [Accessibility::CategoryBreakdownService] dropping unknown…
- content is not a Submission
- " " must be either " " or
- invalid date type: #
- Invalid mode: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/39a4716dd056a2a1.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/content_participation.rb:147
end
private_class_method :build_item
def self.same_workflow_state?(participant, workflow_state)
participant.present? && participant.workflow_state == workflow_state
end
private_class_method :same_workflow_state?
def self.submission_read?(content:, user:)
submission_read_state(content, user) != "unread"
end
def self.submission_item_read?(content:, user:, content_item:)
submission_read_state(content, user, content_item) != "unread"
end
def self.submission_read_state(content, user, content_item = nil)
raise "content is not a Submission" unless content.is_a?(Submission)
raise "#{content_item} is invalid" if content_item.present? && !CONTENT_ITEMS.include?(content_item)
states = if content_item.present?
ContentParticipation.where(content:, user:, content_item:).pluck(:workflow_state)
else
ContentParticipation.where(content:, user:).pluck(:workflow_state)
end
return nil if states.empty?
return "unread" if states.any?("unread")
"read"
end
def self.items_by_submission(participations, workflow_state)
unread_items = {}
participations.each do |cp|
unread_items[cp.content_id] ||= []View on GitHub (pinned to 1c9f0bb801)