{"record":{"id":"9796f03810cac65c","repo":"instructure/canvas-lms","slug":"assignment-tool-mismatch","errorCode":"assignment_tool_mismatch","errorMessage":"Assignment is no longer associated with this tool","messagePattern":"Assignment is no longer associated with this tool","errorType":"error_code","errorClass":"BasicLTI::Errors::InvalidSourceId","httpStatus":null,"severity":"error","filePath":"lib/basic_lti/sourcedid.rb","lineNumber":59,"sourceCode":"      {\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])\n      end\n\n      sourcedid = new(tool, course, assignment, user)","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/basic_lti/sourcedid.rb#L41-L77","documentation":"BasicLti::Sourcedid#validate! raises Errors::InvalidSourceId with :assignment_tool_mismatch when the assignment's external_tool_tag is missing, the configured tool no longer matches the tag's URL (by exact URL or domain) or the tool has been deleted. It guards that the sourcedid's tool is still the tool actually attached to the assignment.","triggerScenarios":"Sourcedid.load! after the teacher re-pointed the assignment at a different tool/URL, changed the tool's configured URL/domain so matches_url? fails, or deleted the tool (workflow_state 'deleted').","commonSituations":"An admin edits the external tool's launch URL or domain in account settings, breaking previously valid launches; a developer configures a replacement tool with the same key but a different domain; the assignment's link was edited to another tool.","solutions":["Re-launch the assignment so a new sourcedid is issued against the current tool binding.","Compare tool.matches_url?(tag.url) / matches_tool_domain?(tag.url) against the assignment's external_tool_tag URL and fix the tool configuration to match.","Restore/re-activate the tool if it was deleted in error (tool.workflow_state).","Update the assignment's external tool tag to point at the correct tool."],"exampleFix":"// before\ntag = assignment.external_tool_tag\n# tool config changed to a new domain; sourcedid now fails\n// after\n# align tool config with the assignment tag, or re-link the assignment\ntag.url == tool.configured_url or assignment.update(external_tool_tag: new_tag)","handlingStrategy":"try-catch","validationCode":"# ruby\ntag = assignment.external_tool_tag\nok = tag && (tool.matches_url?(tag.url, match_queries_exactly: false) || tool.matches_tool_domain?(tag.url)) && tool.workflow_state != 'deleted'","typeGuard":null,"tryCatchPattern":"begin\n  BasicLti::Sourcedid.load!(sourcedid)\nrescue BasicLti::Sourcedid::Errors::InvalidSourceId => e\n  force_relaunch! if e.error_code == :assignment_tool_mismatch\nend","preventionTips":["Keep tool URL/domain configuration stable; coordinate changes with active assignments.","Re-launch after any tool config edit rather than reusing old sourcedids.","Avoid deleting tools with live assignments; deactivate gracefully first."],"tags":["lti","tool-configuration","url-mismatch"],"backgroundTag":"invalid-config-value","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"}