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

assignment_invalid

assignment_invalid

Error message

Assignment is invalid

What it means

BasicLti::Sourcedid#validate! raises Errors::InvalidSourceId with :assignment_invalid when the assignment referenced by the sourcedid token cannot be found (Assignment lookup returned nil). This means the assignment was deleted, or its id in the token no longer resolves, so LTI operations tied to it must stop.

Solutions

  1. Re-launch the tool to get a sourcedid bound to a valid assignment.
  2. Check Assignment.where(id: assignment_id).exists? before calling APIs with the stored sourcedid.
  3. Remove the stale sourcedid from your data store; re-create the assignment in Canvas if it was deleted by mistake.
  4. Verify the sourcedid token was not truncated or corrupted so the correct assignment_id is parsed.

Example fix

// before
sourcedid = BasicLti::Sourcedid.load!(params[:lis_result_sourcedid])
// after
unless Assignment.where(id: parsed_assignment_id).exists?
  Rails.logger.warn("assignment gone; re-launch required")
end
sourcedid = BasicLti::Sourcedid.load!(params[:lis_result_sourcedid])
Defensive patterns

Strategy: validation

Validate before calling

# ruby
Assignment.where(id: assignment_id).exists? or raise "assignment deleted"

Try / catch

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

Prevention

When it happens

Trigger: Sourcedid.load!(sourcedid) with a token whose assignment_id was deleted; grade passback for an assignment removed from the course between launch and the tool call.

Common situations: Teacher deleted or unpublished-and-deleted the external-tool assignment while students still have tool-side grade column data; stale sourcedids stored by an external gradebook syncing after the assignment is gone.

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/3bf54d7f68069cb2. Report an issue: GitHub.

Appendix: source

Thrown at lib/basic_lti/sourcedid.rb:56

    end

    def jwt_payload
      {
        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])

View on GitHub (pinned to 1c9f0bb801)