{"record":{"id":"6e803d5c5234ae02","repo":"instructure/canvas-lms","slug":"assignment-not-configured-for-launches-with-specified-tool","errorCode":null,"errorMessage":"Assignment not configured for launches with specified tool","messagePattern":"Assignment not configured for launches with specified tool","errorType":"exception","errorClass":"Lti::IMS::AdvantageErrors::InvalidLaunchError","httpStatus":null,"severity":"error","filePath":"lib/lti/messages/resource_link_request.rb","lineNumber":107,"sourceCode":"    def unexpanded_custom_parameters\n      # Add in link-specific custom params (e.g. created by deep linking)\n      super.merge(resource_link&.custom || {})\n    end\n\n    def resource_link\n      assignment_resource_link || @opts[:resource_link]\n    end\n\n    def assignment_resource_link\n      return if @assignment.nil?\n\n      unless defined?(@assignment_resource_link)\n        launch_error = Lti::IMS::AdvantageErrors::InvalidLaunchError\n        unless @assignment.external_tool?\n          raise launch_error.new(nil, api_message: \"Assignment not configured for external tool launches\")\n        end\n        unless Lti::ToolFinder.from_assignment(@assignment) == @tool\n          raise launch_error.new(nil, api_message: \"Assignment not configured for launches with specified tool\")\n        end\n\n        @assignment_resource_link = line_item_for_assignment&.resource_link\n      end\n\n      @assignment_resource_link\n    end\n\n    def line_item_for_assignment\n      @line_item_for_assignment ||= @assignment&.line_items&.find(&:assignment_line_item?)\n    end\n\n    # follows the spec at https://www.imsglobal.org/spec/lti-ags/v2p0/#assignment-and-grade-service-claim\n    # and only adds the 'lineitem' property \"when the LTI message is launching a resource associated\n    # to one and only one line item\"\n    def add_line_item_url_to_ags_claim!\n      return if line_item_for_assignment.blank?\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/lti/messages/resource_link_request.rb#L89-L125","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set the assignment's submission_types to 'external_tool' and attach an external tool tag pointing at the target tool.","Launch from the tool (client_id) actually configured on the assignment's external_tool tag.","Rebuild the assignment's tool tag after a course copy if it was dropped or re-linked to a different tool.","Handle InvalidLaunchError in the launch controller and surface a friendly error to the LTI platform."],"exampleFix":"// before (assignment not a tool assignment)\nassignment.update(submission_types: 'online_text_entry')\n// after\nassignment.update(submission_types: 'external_tool')\nassignment.build_external_tool_tag(content: tool, url: tool.config_for(domain).first[:url]).save!","handlingStrategy":"validation","validationCode":"raise ArgumentError unless assignment.external_tool?\nraise ArgumentError unless Lti::ToolFinder.from_assignment(assignment) == tool","typeGuard":"def launchable_with?(assignment, tool)\n  assignment.external_tool? && Lti::ToolFinder.from_assignment(assignment) == tool\nend","tryCatchPattern":"begin\n  assignment_resource_link\nrescue Lti::IMS::AdvantageErrors::InvalidLaunchError => e\n  Rails.logger.warn(\"launch rejected: #{e.message}\")\n  render json: { errors: e.to_s }, status: :unprocessable_entity\nend","preventionTips":["Always create tool assignments with submission_types: 'external_tool' and a saved external tool tag.","After course copies/imports, verify external tool tags still point at the intended tool.","Launch only with the client_id/tool registered on the assignment.","Catch InvalidLaunchError at the launch controller boundary and return a friendly API error."],"tags":["lti","launch-validation","assignment-config"],"backgroundTag":"invalid-state-transition","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}