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
- Re-launch the tool so the client receives a fresh (JWT-based) sourcedid.
- Confirm the tool still exists and check Lti::ToolFinder.find_by(id: tool_id) on the correct shard.
- Purge stale legacy sourcedids from your data store.
- 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
- Migrate off legacy sourcedids to JWT-based ones.
- Verify tools exist on the correct shard before cross-environment operations.
- Purge stored sourcedids when their tool is decommissioned.
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
- Access token expired
- Access token invalid - signature likely incorrect
- assignment_invalid
- Assignment not configured for external tool launches
- Assignment not configured for launches with specified tool
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)