instructure/canvas-lms · error · Lti::IMS::AdvantageErrors::InvalidLaunchError

Assignment not configured for launches with specified tool

Error message

Assignment not configured for launches with specified tool

What it means

AssignmentResourceLinkRequest#assignment_resource_link raises InvalidLaunchError when the assignment targeted by an LTI resource link launch cannot be launched with the current tool. Two checks fail it: the assignment must be an external-tool assignment, and the tool resolved from the assignment must match the launching tool. Otherwise the launch is rejected before any resource link is resolved.

Solutions

  1. Set the assignment's submission_types to 'external_tool' and attach an external tool tag pointing at the target tool.
  2. Launch from the tool (client_id) actually configured on the assignment's external_tool tag.
  3. Rebuild the assignment's tool tag after a course copy if it was dropped or re-linked to a different tool.
  4. Handle InvalidLaunchError in the launch controller and surface a friendly error to the LTI platform.

Example fix

// before (assignment not a tool assignment)
assignment.update(submission_types: 'online_text_entry')
// after
assignment.update(submission_types: 'external_tool')
assignment.build_external_tool_tag(content: tool, url: tool.config_for(domain).first[:url]).save!
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError unless assignment.external_tool?
raise ArgumentError unless Lti::ToolFinder.from_assignment(assignment) == tool

Type guard

def launchable_with?(assignment, tool)
  assignment.external_tool? && Lti::ToolFinder.from_assignment(assignment) == tool
end

Try / catch

begin
  assignment_resource_link
rescue Lti::IMS::AdvantageErrors::InvalidLaunchError => e
  Rails.logger.warn("launch rejected: #{e.message}")
  render json: { errors: e.to_s }, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: POSTing an LTI launch for an assignment whose submission_types are not 'external_tool', or launching with a different LTI::Tool (client_id/developer_key) than the one configured on the assignment's external tool tag.

Common situations: Course copy/import loses or re-points the external tool tag so the assignment resolves to a different tool; devs reuse a tool's launch URL on a regular (online) assignment; multiple tools installed and the launch comes through the wrong client_id.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at lib/lti/messages/resource_link_request.rb:107

    def unexpanded_custom_parameters
      # Add in link-specific custom params (e.g. created by deep linking)
      super.merge(resource_link&.custom || {})
    end

    def resource_link
      assignment_resource_link || @opts[:resource_link]
    end

    def assignment_resource_link
      return if @assignment.nil?

      unless defined?(@assignment_resource_link)
        launch_error = Lti::IMS::AdvantageErrors::InvalidLaunchError
        unless @assignment.external_tool?
          raise launch_error.new(nil, api_message: "Assignment not configured for external tool launches")
        end
        unless Lti::ToolFinder.from_assignment(@assignment) == @tool
          raise launch_error.new(nil, api_message: "Assignment not configured for launches with specified tool")
        end

        @assignment_resource_link = line_item_for_assignment&.resource_link
      end

      @assignment_resource_link
    end

    def line_item_for_assignment
      @line_item_for_assignment ||= @assignment&.line_items&.find(&:assignment_line_item?)
    end

    # follows the spec at https://www.imsglobal.org/spec/lti-ags/v2p0/#assignment-and-grade-service-claim
    # and only adds the 'lineitem' property "when the LTI message is launching a resource associated
    # to one and only one line item"
    def add_line_item_url_to_ags_claim!
      return if line_item_for_assignment.blank?

View on GitHub (pinned to 1c9f0bb801)