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

assignment_tool_mismatch

assignment_tool_mismatch

Error message

Assignment is no longer associated with this tool

What it means

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.

Solutions

  1. Re-launch the assignment so a new sourcedid is issued against the current tool binding.
  2. 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.
  3. Restore/re-activate the tool if it was deleted in error (tool.workflow_state).
  4. Update the assignment's external tool tag to point at the correct tool.

Example fix

// before
tag = assignment.external_tool_tag
# tool config changed to a new domain; sourcedid now fails
// after
# align tool config with the assignment tag, or re-link the assignment
tag.url == tool.configured_url or assignment.update(external_tool_tag: new_tag)
Defensive patterns

Strategy: try-catch

Validate before calling

# ruby
tag = assignment.external_tool_tag
ok = tag && (tool.matches_url?(tag.url, match_queries_exactly: false) || tool.matches_tool_domain?(tag.url)) && tool.workflow_state != 'deleted'

Try / catch

begin
  BasicLti::Sourcedid.load!(sourcedid)
rescue BasicLti::Sourcedid::Errors::InvalidSourceId => e
  force_relaunch! if e.error_code == :assignment_tool_mismatch
end

Prevention

When it happens

Trigger: 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').

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at lib/basic_lti/sourcedid.rb:59

      {
        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
        user = course.student_enrollments.active.find_by(user_id: token[:user_id])&.user
        assignment = course.assignments.active.find_by(id: token[:assignment_id])
      end

      sourcedid = new(tool, course, assignment, user)

View on GitHub (pinned to 1c9f0bb801)