{"record":{"id":"aca39821aadea20a","repo":"instructure/canvas-lms","slug":"fetching-data-from-tool-name-timed-out","errorCode":null,"errorMessage":"Fetching data from #{@tool.name} timed out.","messagePattern":"Fetching data from #(.+?) timed out\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"app/models/lti/content_migration_service/exporter.rb","lineNumber":76,"sourceCode":"      rescue Timeout::Error\n        false\n      end\n\n      def retrieve_export\n        return nil if @export_status == FAILED_STATUS\n\n        response = Canvas.retriable(on: Timeout::Error) do\n          InstrumentTLSCiphers.without_tls_metrics do\n            CanvasHttp.get(@fetch_url, base_request_headers)\n          end\n        end\n        if response.code.to_i == 200\n          JSON.parse(response.body)\n        else\n          raise \"Unable to fetch export data from #{@status_url}: #{response.code} #{response.message}. (#{response.body})\"\n        end\n      rescue Timeout::Error\n        raise \"Fetching data from #{@tool.name} timed out.\"\n      end\n\n      def start!\n        return if defined? @status_url\n\n        InstrumentTLSCiphers.without_tls_metrics do\n          response = Canvas.retriable(on: Timeout::Error) do\n            case export_format\n            when JSON_FORMAT\n              CanvasHttp.post(export_start_url, base_request_headers, body: start_export_post_body.to_json, content_type: \"application/json\")\n            else\n              CanvasHttp.post(export_start_url, base_request_headers, form_data: Rack::Utils.build_nested_query(start_export_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?","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/lti/content_migration_service/exporter.rb#L58-L94","documentation":"Raised by Lti::ContentMigrationService::Exporter#retrieve_export when the CanvasHttp.get call to the tool's fetch_url raises Timeout::Error even after Canvas.retriable retries. It means the external tool failed to return its exported content payload within the HTTP timeout, so the course copy/export cannot retrieve the export data. The tool name is interpolated so operators know which third-party integration stalled.","triggerScenarios":"Calling retrieve_export after a successful start! when CanvasHttp.get(@fetch_url) times out repeatedly (Canvas.retriable exhausts its retries on Timeout::Error). The response never gets a status code, so the non-200 branch is bypassed and the rescue re-raises as this message.","commonSituations":"The external tool's export endpoint is slow, hung, or overloaded during a course copy; network/firewall issues between Canvas and the tool host; the tool acknowledges the export start but never finishes generating the payload at fetch_url; DNS or proxy latency in self-hosted environments.","solutions":["Retry the course copy/export later — the tool may recover; check the tool vendor's status first.","Verify the tool's content_migration.export_start_url/fetch_url endpoint is reachable and fast (curl it from the Canvas web/app server).","Check network paths: firewalls, proxies, and DNS between Canvas and the tool host; increase CanvasHttp/open_timeout ceilings if the tool is legitimately slow.","Inspect the external tool's server logs for the export job corresponding to this fetch request.","As a workaround, export without the external tool's content or reconfigure the tool's content_migration settings."],"exampleFix":"// before (tool-side: slow synchronous export generation)\nexpensive_export_build_blocking_request\n// after\nenqueue_export_job_and_return_status_url_immediately","handlingStrategy":"retry","validationCode":"# before relying on the export, verify the tool's fetch endpoint answers quickly\nuri = URI(exporter_start_url) # or a health endpoint\nbegin\n  resp = Net::HTTP.start(uri.host, uri.port, open_timeout: 5, read_timeout: 10) { |h| h.get('/') }\n  raise 'tool host unreachable' unless resp.is_a?(Net::HTTPSuccess)\nrescue Net::OpenTimeout, Net::ReadTimeout\n  raise 'tool host too slow; aborting export before timeout path'\nend","typeGuard":null,"tryCatchPattern":"begin\n  data = exporter.retrieve_export\nrescue RuntimeError => e\n  if e.message.start_with?('Fetching data from') && e.message.end_with?('timed out.')\n    Rails.logger.warn(\"External tool export fetch timed out: #{e.message}\")\n    # surface retry UI or fall back to export without this tool's content\n  else\n    raise\n  end\nend","preventionTips":["Pre-flight check the tool host's reachability and latency before starting large course exports.","Configure Canvas.retriable timeouts appropriate to the tool's realistic export size.","Monitor external tool response times and alert before user-facing course copies fail.","Prefer tools whose content migration export is asynchronous (status_url polling)."],"tags":["network","timeout","lti","http","course-export"],"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"}