instructure/canvas-lms · error · UnknownTypeError
Unable to determine asset type for Lti::Asset id=#
Error message
Unable to determine asset type for Lti::Asset id=#{id}. The referred discussion_entry_version or the submission has been probably deleted. What it means
Lti::Asset#asset_type infers the asset's type from whichever foreign key is present (submission, attachment, discussion_entry_version). If none of the referenced records' ids is set — typically because the underlying submission or discussion_entry_version was deleted — it logs the anomaly and raises UnknownTypeError, since an Lti::Asset with no resolvable backing record cannot be typed.
Solutions
- Purge or ignore Lti::Asset rows whose backing submission/discussion_entry_version no longer exists; guard callers with asset-aware existence checks.
- Restore the deleted submission/discussion entry if the data should exist.
- Wrap asset_type access in a rescue of Lti::Asset::UnknownTypeError and skip the asset.
- Add cleanup callbacks so deleting a Submission/DiscussionEntryVersion also deletes dependent Lti::Asset rows.
Example fix
// before
atype = asset.asset_type
// after
atype = asset.asset_type
rescue Lti::Asset::UnknownTypeError => e
Rails.logger.warn("Skipping stale Lti::Asset: #{e.message}")
next Defensive patterns
Strategy: try-catch
Validate before calling
next if asset.submission_id.blank? && asset.attachment_id.blank? && asset.discussion_entry_version_id.blank? atype = asset.asset_type
Type guard
def resolvable?(asset) = asset.submission_id.present? || asset.attachment_id.present? || asset.discussion_entry_version_id.present?
Try / catch
begin
atype = asset.asset_type
rescue Lti::Asset::UnknownTypeError => e
Rails.logger.warn("Skipping stale asset: #{e.message}")
next
end Prevention
- Add dependent-destroy callbacks so Lti::Asset rows are removed with their Submission/DiscussionEntryVersion
- Filter out assets whose backing records were deleted before processing batches
- Monitor the Lti::Asset unknown-type log line as a data-integrity signal
When it happens
Trigger: Reading an Lti::Asset whose submission_id, attachment_id, and discussion_entry_version_id are all blank — e.g. after the Submission or DiscussionEntryVersion it pointed at was soft/hard deleted while the asset row (or a reference to it) persists.
Common situations: AGS/LTI score reporting for a submission deleted by a student or cleanup job; discussion topics/entries removed while LTI assets remain; speedgrader or analytics jobs touching stale assets.
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/6f85655e5d37e7a9.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/lti/asset.rb:120
# In theory, submissions can be deleted, to not break the foreign key constraint we set the submission_id to null
# when the submission is hard deleted with dependent: :nullify in the Submission model.
return "deleted"
end
# if submission_attempt is set, it's RCE content
if submission_attempt.present?
TYPE_TEXT_ENTRY
# if attachment_id is set, it's a file attachment of a submission
elsif attachment_id.present?
TYPE_ATTACHMENT
# if discussion_entry_version_id is set, it's a discussion entry (comment)
elsif discussion_entry_version_id.present?
TYPE_DISCUSSION_ENTRY
else
Rails.logger.error(
"Lti::Asset unknown type id=#{id}, submission_id=#{submission_id}, attachment_id=#{attachment_id}, submission_attempt=#{submission_attempt}, discussion_entry_version_id=#{discussion_entry_version_id}"
)
raise UnknownTypeError, "Unable to determine asset type for Lti::Asset id=#{id}. The referred discussion_entry_version or the submission has been probably deleted."
end
end
# Returns the submission claim ID for this asset.
# Discussion entries use DiscussionEntryVersion#lti_submission_claim_id.
# All other types delegate to Submission#lti_attempt_id with the asset's attempt number.
def submission_lti_claim_id
return nil unless submission
# Discussion Entry
if discussion_entry?
discussion_entry_version.lti_submission_claim_id(submission)
# Attachment of discussion entry
elsif attachment_id.present? && submission.submission_type == "discussion_topic"
# attachment is stored on discussion_entry not discussion_entry_version,
# so we find the most recent entry and version for the submission that contains the attachment
# and send that submission in the claim (also that is what we show in SG)
entry = DiscussionEntry.where(View on GitHub (pinned to 1c9f0bb801)