instructure/canvas-lms · error · BasicLTI::Errors::InvalidSourceId

course_invalid

course_invalid

Error message

Course is invalid

What it means

BasicLTI::Sourcedid#validate! re-checks at load time that the course, user, and assignment referenced by the sourcedid still exist and match the tool. The first check raises InvalidSourceId with code course_invalid when the course record encoded in the sourcedid's JWT can no longer be found (e.g. deleted or on an inaccessible shard).

Solutions

  1. Re-launch the assignment so a fresh sourcedid referencing an existing course is issued
  2. Confirm the course still exists (Course.where(id: ...)) before the TP submits grades
  3. Check whether the course was soft-deleted (workflow_state) and restore it if needed
  4. Rescue BasicLTI::Errors::InvalidSourceId in the outcomes endpoint and return an appropriate failure to the TP

Example fix

// before
BasicLTI::BasicOutcomes.process_request(tool, xml) // raises if course deleted
// after
begin
  BasicLTI::BasicOutcomes.process_request(tool, xml)
rescue BasicLTI::Errors::InvalidSourceId => e
  logger.warn("sourcedid invalid: #{e.message}")
  return failure response
end
Defensive patterns

Strategy: try-catch

Validate before calling

payload = BasicLTI::Sourcedid.jwt_payload_for(course, user, assignment)
raise "course missing" unless Course.exists?(payload[:course_id])

Try / catch

begin
  BasicLTI::BasicOutcomes.process_request(tool, xml)
rescue BasicLTI::Errors::InvalidSourceId => e
  return failure xml with e.code (e.g. course_invalid)
end

Prevention

When it happens

Trigger: A grade-passback request arrives with a sourcedid whose course was deleted after launch; course_id in the JWT not found in the database; cross-shard sourcedid loaded without the course present on the target shard.

Common situations: Courses deleted/concluded between LTI launch and grade submission; TP delaying grade submissions past course deletion; test-environment data not matching production sourcedids.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at lib/basic_lti/sourcedid.rb:54

      )
      Canvas::Security.base64_encode(crypted_token)
    end

    def jwt_payload
      {
        iss: "Canvas",
        aud: ["Instructure"],
        iat: Time.zone.now.to_i,
        tool_id: tool.id,
        course_id: course.id,
        assignment_id: assignment.id,
        user_id: user.id,
      }
    end
    private :jwt_payload

    def validate!
      raise Errors::InvalidSourceId.new("Course is invalid", :course_invalid) unless course
      raise Errors::InvalidSourceId.new("User is no longer in course", :user_not_in_course) unless user
      raise Errors::InvalidSourceId.new("Assignment is invalid", :assignment_invalid) unless assignment

      tag = assignment.external_tool_tag
      raise Errors::InvalidSourceId.new("Assignment is no longer associated with this tool", :assignment_tool_mismatch) unless tag &&
                                                                                                                               (tool.matches_url?(tag.url, match_queries_exactly: false) || tool.matches_tool_domain?(tag.url)) &&
                                                                                                                               tool.workflow_state != "deleted"
    end

    def self.load!(sourcedid_string)
      raise Errors::InvalidSourceId.new("Invalid sourcedid", :sourcedid_invalid) if sourcedid_string.blank?

      token = load_from_legacy_sourcedid!(sourcedid_string) ||
              token_from_sourcedid!(sourcedid_string)

      tool = Lti::ToolFinder.find_by(id: token[:tool_id])
      course = Course.active.find_by(id: token[:course_id])
      if course

View on GitHub (pinned to 1c9f0bb801)