{"record":{"id":"427a057cc0fe192e","repo":"instructure/canvas-lms","slug":"course-invalid","errorCode":"course_invalid","errorMessage":"Course is invalid","messagePattern":"Course is invalid","errorType":"error_code","errorClass":"BasicLTI::Errors::InvalidSourceId","httpStatus":null,"severity":"error","filePath":"lib/basic_lti/sourcedid.rb","lineNumber":54,"sourceCode":"      )\n      Canvas::Security.base64_encode(crypted_token)\n    end\n\n    def jwt_payload\n      {\n        iss: \"Canvas\",\n        aud: [\"Instructure\"],\n        iat: Time.zone.now.to_i,\n        tool_id: tool.id,\n        course_id: course.id,\n        assignment_id: assignment.id,\n        user_id: user.id,\n      }\n    end\n    private :jwt_payload\n\n    def validate!\n      raise Errors::InvalidSourceId.new(\"Course is invalid\", :course_invalid) unless course\n      raise Errors::InvalidSourceId.new(\"User is no longer in course\", :user_not_in_course) unless user\n      raise Errors::InvalidSourceId.new(\"Assignment is invalid\", :assignment_invalid) unless assignment\n\n      tag = assignment.external_tool_tag\n      raise Errors::InvalidSourceId.new(\"Assignment is no longer associated with this tool\", :assignment_tool_mismatch) unless tag &&\n                                                                                                                               (tool.matches_url?(tag.url, match_queries_exactly: false) || tool.matches_tool_domain?(tag.url)) &&\n                                                                                                                               tool.workflow_state != \"deleted\"\n    end\n\n    def self.load!(sourcedid_string)\n      raise Errors::InvalidSourceId.new(\"Invalid sourcedid\", :sourcedid_invalid) if sourcedid_string.blank?\n\n      token = load_from_legacy_sourcedid!(sourcedid_string) ||\n              token_from_sourcedid!(sourcedid_string)\n\n      tool = Lti::ToolFinder.find_by(id: token[:tool_id])\n      course = Course.active.find_by(id: token[:course_id])\n      if course","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/basic_lti/sourcedid.rb#L36-L72","documentation":"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).","triggerScenarios":"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.","commonSituations":"Courses deleted/concluded between LTI launch and grade submission; TP delaying grade submissions past course deletion; test-environment data not matching production sourcedids.","solutions":["Re-launch the assignment so a fresh sourcedid referencing an existing course is issued","Confirm the course still exists (Course.where(id: ...)) before the TP submits grades","Check whether the course was soft-deleted (workflow_state) and restore it if needed","Rescue BasicLTI::Errors::InvalidSourceId in the outcomes endpoint and return an appropriate failure to the TP"],"exampleFix":"// before\nBasicLTI::BasicOutcomes.process_request(tool, xml) // raises if course deleted\n// after\nbegin\n  BasicLTI::BasicOutcomes.process_request(tool, xml)\nrescue BasicLTI::Errors::InvalidSourceId => e\n  logger.warn(\"sourcedid invalid: #{e.message}\")\n  return failure response\nend","handlingStrategy":"try-catch","validationCode":"payload = BasicLTI::Sourcedid.jwt_payload_for(course, user, assignment)\nraise \"course missing\" unless Course.exists?(payload[:course_id])","typeGuard":null,"tryCatchPattern":"begin\n  BasicLTI::BasicOutcomes.process_request(tool, xml)\nrescue BasicLTI::Errors::InvalidSourceId => e\n  return failure xml with e.code (e.g. course_invalid)\nend","preventionTips":["Re-launch LTI assignments after course changes","Check course existence before delayed grade submissions","Restore soft-deleted courses when delayed passbacks are expected","Handle per-code failures distinctly in the TP response"],"tags":["lti","sourcedid","basic-outcomes","course"],"backgroundTag":"record-not-found","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"}