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

tool_invalid

tool_invalid

Error message

Tool is invalid

What it means

Sourcedid.load_from_legacy_sourcedid! raises Errors::InvalidSourceId with :tool_invalid when a legacy-format sourcedid matches SOURCE_ID_REGEX but the tool id embedded in it (md[1]) does not resolve via Lti::ToolFinder.find_by. The legacy sourcedid references a tool that no longer exists, so its signature cannot even be verified.

Solutions

  1. Re-launch the tool so the client receives a fresh (JWT-based) sourcedid.
  2. Confirm the tool still exists and check Lti::ToolFinder.find_by(id: tool_id) on the correct shard.
  3. Purge stale legacy sourcedids from your data store.
  4. Verify the sourcedid string was not corrupted so md[1] parses to the right id.

Example fix

// before
tool_id = sourcedid.split('-').first
# assumes tool exists
// after
raise "stale sourcedid: tool gone" unless Lti::ToolFinder.find_by(id: tool_id)
Defensive patterns

Strategy: try-catch

Validate before calling

# ruby
tool_id = sourcedid.scan(/\A(\d+)-/).flatten.first
Lti::ToolFinder.find_by(id: tool_id).present?

Try / catch

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

Prevention

When it happens

Trigger: Loading a legacy OAuth-signed sourcedid whose first segment is a tool id that was deleted, or from a different shard than the one being queried, or with a corrupted id segment.

Common situations: Years-old sourcedids stored by external gradebooks referencing tools removed from Canvas; sharding/multi-tenant setups where find_by runs on the wrong shard; manual construction of sourcedid strings in tests/scripts.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at lib/basic_lti/sourcedid.rb:87

      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)
      sourcedid.validate!
      sourcedid
    end

    def self.load_from_legacy_sourcedid!(sourcedid)
      token = nil
      md = sourcedid.match(SOURCE_ID_REGEX)
      if md
        tool = Lti::ToolFinder.find_by(id: md[1])
        raise Errors::InvalidSourceId.new("Tool is invalid", :tool_invalid) unless tool

        new_encoding = [md[1], md[2], md[3], md[4]].join("-")
        raise Errors::InvalidSourceId.new("Invalid signature", :signature_invalid) unless Canvas::Security
                                                                                          .verify_hmac_sha1(md[5], new_encoding, key: tool.shard.settings[:encryption_key])

        token = { tool_id: md[1].to_i, course_id: md[2], assignment_id: md[3], user_id: md[4] }
      end
      token
    end

    def self.token_from_sourcedid!(sourcedid)
      Canvas::Security.decrypt_encrypted_jwt(
        Canvas::Security.base64_decode(sourcedid),
        signing_secret,
        encryption_secret
      )
    rescue JSON::JWT::InvalidFormat
      raise Errors::InvalidSourceId.new("Invalid sourcedid", :sourcedid_invalid)

View on GitHub (pinned to 1c9f0bb801)