{"record":{"id":"3bf54d7f68069cb2","repo":"instructure/canvas-lms","slug":"assignment-invalid","errorCode":"assignment_invalid","errorMessage":"Assignment is invalid","messagePattern":"Assignment is invalid","errorType":"error_code","errorClass":"BasicLTI::Errors::InvalidSourceId","httpStatus":null,"severity":"error","filePath":"lib/basic_lti/sourcedid.rb","lineNumber":56,"sourceCode":"    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\n        user = course.student_enrollments.active.find_by(user_id: token[:user_id])&.user\n        assignment = course.assignments.active.find_by(id: token[:assignment_id])","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/basic_lti/sourcedid.rb#L38-L74","documentation":"BasicLti::Sourcedid#validate! raises Errors::InvalidSourceId with :assignment_invalid when the assignment referenced by the sourcedid token cannot be found (Assignment lookup returned nil). This means the assignment was deleted, or its id in the token no longer resolves, so LTI operations tied to it must stop.","triggerScenarios":"Sourcedid.load!(sourcedid) with a token whose assignment_id was deleted; grade passback for an assignment removed from the course between launch and the tool call.","commonSituations":"Teacher deleted or unpublished-and-deleted the external-tool assignment while students still have tool-side grade column data; stale sourcedids stored by an external gradebook syncing after the assignment is gone.","solutions":["Re-launch the tool to get a sourcedid bound to a valid assignment.","Check Assignment.where(id: assignment_id).exists? before calling APIs with the stored sourcedid.","Remove the stale sourcedid from your data store; re-create the assignment in Canvas if it was deleted by mistake.","Verify the sourcedid token was not truncated or corrupted so the correct assignment_id is parsed."],"exampleFix":"// before\nsourcedid = BasicLti::Sourcedid.load!(params[:lis_result_sourcedid])\n// after\nunless Assignment.where(id: parsed_assignment_id).exists?\n  Rails.logger.warn(\"assignment gone; re-launch required\")\nend\nsourcedid = BasicLti::Sourcedid.load!(params[:lis_result_sourcedid])","handlingStrategy":"validation","validationCode":"# ruby\nAssignment.where(id: assignment_id).exists? or raise \"assignment deleted\"","typeGuard":null,"tryCatchPattern":"begin\n  BasicLti::Sourcedid.load!(sourcedid)\nrescue BasicLti::Sourcedid::Errors::InvalidSourceId => e\n  drop_sync_entry if e.error_code == :assignment_invalid\nend","preventionTips":["Check Assignment.exists? before passback for stored sourcedids.","Clean up tool-side grade columns when the Canvas assignment is deleted via live events.","Never hard-code assignment ids into sourcedid-like strings."],"tags":["lti","assignment","stale-reference"],"backgroundTag":"entity-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"}