{"record":{"id":"15a1516530c889bd","repo":"instructure/canvas-lms","slug":"unable-to-start-import-for-external-tool-tool-name-request","errorCode":null,"errorMessage":"Unable to start import for external tool #{@tool.name}, request timed out.","messagePattern":"Unable to start import for external tool #(.+?), request timed out\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"app/models/lti/content_migration_service/importer.rb","lineNumber":63,"sourceCode":"          case import_format\n          when JSON_FORMAT\n            CanvasHttp.post(import_start_url, base_request_headers, body: post_body.to_json, content_type: \"application/json\")\n          else\n            CanvasHttp.post(import_start_url, base_request_headers, form_data: Rack::Utils.build_nested_query(post_body))\n          end\n        end\n        case response.code.to_i\n        when (200..201)\n          parsed_response = JSON.parse(response.body)\n          unless parsed_response.empty?\n            @status_url = parsed_response[\"status_url\"]\n          end\n        else\n          raise \"Unable to start import for external tool #{@tool.name} (#{response.code})\"\n        end\n        self\n      rescue Timeout::Error\n        raise \"Unable to start import for external tool #{@tool.name}, request timed out.\"\n      end\n\n      def import_completed?\n        InstrumentTLSCiphers.without_tls_metrics do\n          response = Canvas.retriable(on: Timeout::Error) { CanvasHttp.get(@status_url, base_request_headers) } if @status_url\n          if response&.code.to_i == 200\n            parsed_response = JSON.parse(response.body)\n            @export_status = parsed_response[\"status\"]\n            case @export_status\n            when SUCCESSFUL_STATUS\n              true\n            when FAILED_STATUS\n              raise parsed_response[\"message\"]\n            else\n              false\n            end\n          end\n        end","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/lti/content_migration_service/importer.rb#L45-L81","documentation":"Raised by Lti::ContentMigrationService::Importer#send_imported_content when the initial POST of imported content to the tool's import_start_url raises Timeout::Error and Canvas.retriable's retries are exhausted. The import for the external tool never got a response, so no status_url exists and the import cannot proceed. Distinct from error 996: the tool never responded at all rather than returning an error status.","triggerScenarios":"send_imported_content's CanvasHttp.post(import_start_url, ...) times out (after Canvas.retriable retries on Timeout::Error) — typically the tool host is unreachable, hung, or too slow processing the posted content payload.","commonSituations":"Tool server down or overloaded during a course copy; large content payloads causing long tool-side processing; network partition/firewall silently dropping packets to the tool host; DNS failures in self-hosted environments; tool's import endpoint doing synchronous heavy work.","solutions":["Retry the course import — transient timeouts often resolve.","Verify the tool's import_start_url host is up and reachable (curl/ping from the Canvas server).","Check network path: firewall rules, proxy config, DNS between Canvas and the tool host.","Ask the tool vendor to make import asynchronous (return status_url immediately, process in background) if large payloads cause slow responses.","Inspect Canvas and tool server logs around the timeout timestamp to see where the request stalled."],"exampleFix":"null","handlingStrategy":"retry","validationCode":"# verify the tool host accepts connections before posting content\nuri = URI(import_start_url)\nbegin\n  Net::HTTP.start(uri.host, uri.port, open_timeout: 5, read_timeout: 10) { |h| h.head('/') }\nrescue Net::OpenTimeout, Net::ReadTimeout, SocketError\n  raise 'tool host unreachable; skip import attempt now'\nend","typeGuard":null,"tryCatchPattern":"begin\n  importer.send_imported_content(course, migration, content)\nrescue RuntimeError => e\n  if e.message.include?('request timed out')\n    # exponential backoff retry, then surface a user-facing 'tool unavailable' message\n    retry_later_with_backoff(importer)\n  else\n    raise\n  end\nend","preventionTips":["Health-check the tool host before launching course copies/imports.","Prefer vendors with asynchronous import endpoints that return status_url immediately.","Watch for network changes (proxies, firewalls) between Canvas and tool hosts.","Set retry budgets so a dead tool fails fast instead of stalling the import job."],"tags":["network","timeout","lti","http","course-import"],"backgroundTag":"request-timeout","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"}