instructure/canvas-lms · error · RuntimeError

Fetching data from #

Error message

Fetching data from #{@tool.name} timed out.

What it means

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.

Solutions

  1. Retry the course copy/export later — the tool may recover; check the tool vendor's status first.
  2. Verify the tool's content_migration.export_start_url/fetch_url endpoint is reachable and fast (curl it from the Canvas web/app server).
  3. Check network paths: firewalls, proxies, and DNS between Canvas and the tool host; increase CanvasHttp/open_timeout ceilings if the tool is legitimately slow.
  4. Inspect the external tool's server logs for the export job corresponding to this fetch request.
  5. As a workaround, export without the external tool's content or reconfigure the tool's content_migration settings.

Example fix

// before (tool-side: slow synchronous export generation)
expensive_export_build_blocking_request
// after
enqueue_export_job_and_return_status_url_immediately
Defensive patterns

Strategy: retry

Validate before calling

# before relying on the export, verify the tool's fetch endpoint answers quickly
uri = URI(exporter_start_url) # or a health endpoint
begin
  resp = Net::HTTP.start(uri.host, uri.port, open_timeout: 5, read_timeout: 10) { |h| h.get('/') }
  raise 'tool host unreachable' unless resp.is_a?(Net::HTTPSuccess)
rescue Net::OpenTimeout, Net::ReadTimeout
  raise 'tool host too slow; aborting export before timeout path'
end

Try / catch

begin
  data = exporter.retrieve_export
rescue RuntimeError => e
  if e.message.start_with?('Fetching data from') && e.message.end_with?('timed out.')
    Rails.logger.warn("External tool export fetch timed out: #{e.message}")
    # surface retry UI or fall back to export without this tool's content
  else
    raise
  end
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at app/models/lti/content_migration_service/exporter.rb:76

      rescue Timeout::Error
        false
      end

      def retrieve_export
        return nil if @export_status == FAILED_STATUS

        response = Canvas.retriable(on: Timeout::Error) do
          InstrumentTLSCiphers.without_tls_metrics do
            CanvasHttp.get(@fetch_url, base_request_headers)
          end
        end
        if response.code.to_i == 200
          JSON.parse(response.body)
        else
          raise "Unable to fetch export data from #{@status_url}: #{response.code} #{response.message}. (#{response.body})"
        end
      rescue Timeout::Error
        raise "Fetching data from #{@tool.name} timed out."
      end

      def start!
        return if defined? @status_url

        InstrumentTLSCiphers.without_tls_metrics do
          response = Canvas.retriable(on: Timeout::Error) do
            case export_format
            when JSON_FORMAT
              CanvasHttp.post(export_start_url, base_request_headers, body: start_export_post_body.to_json, content_type: "application/json")
            else
              CanvasHttp.post(export_start_url, base_request_headers, form_data: Rack::Utils.build_nested_query(start_export_post_body))
            end
          end
          case response.code.to_i
          when (200..201)
            parsed_response = JSON.parse(response.body)
            unless parsed_response.empty?

View on GitHub (pinned to 1c9f0bb801)