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
- 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.
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
- 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).
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- could not retrieve configuration, the server response timed…
- Unable to start import for external tool #
- Couldn't send LTI AGS grade progress metric
- failed to load page view history due to service timeout
- Unable to fetch export data from #
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)